taler-exchange-httpd_reserves_purse.c (26453B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2022-2024 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 "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 <pthread.h> 29 #include "taler/taler_json_lib.h" 30 #include "taler/taler_kyclogic_lib.h" 31 #include "taler/taler_mhd_lib.h" 32 #include "taler-exchange-httpd_common_kyc.h" 33 #include "taler-exchange-httpd_reserves_purse.h" 34 #include "taler-exchange-httpd_responses.h" 35 #include "taler/taler_exchangedb_lib.h" 36 #include "taler-exchange-httpd_keys.h" 37 38 39 /** 40 * Closure for #purse_transaction. 41 */ 42 struct ReservePurseContext 43 { 44 45 /** 46 * Kept in a DLL. 47 */ 48 struct ReservePurseContext *next; 49 50 /** 51 * Kept in a DLL. 52 */ 53 struct ReservePurseContext *prev; 54 55 /** 56 * Our request context. 57 */ 58 struct TEH_RequestContext *rc; 59 60 /** 61 * Handle for legitimization check. 62 */ 63 struct TEH_LegitimizationCheckHandle *lch; 64 65 /** 66 * Response to return. Note that the response must 67 * be queued or destroyed by the callee. NULL 68 * if the legitimization check was successful and the handler should return 69 * a handler-specific result. 70 */ 71 struct MHD_Response *response; 72 73 /** 74 * Payto URI for the reserve. 75 */ 76 struct TALER_NormalizedPayto payto_uri; 77 78 /** 79 * Public key of the account (reserve) we are creating a purse for. 80 */ 81 union TALER_AccountPublicKeyP account_pub; 82 83 /** 84 * Signature of the reserve affirming the merge. 85 */ 86 struct TALER_ReserveSignatureP reserve_sig; 87 88 /** 89 * Purse fee the client is willing to pay. 90 */ 91 struct TALER_Amount purse_fee; 92 93 /** 94 * Total amount already put into the purse. 95 */ 96 struct TALER_Amount deposit_total; 97 98 /** 99 * Merge time. 100 */ 101 struct GNUNET_TIME_Timestamp merge_timestamp; 102 103 /** 104 * Our current time. 105 */ 106 struct GNUNET_TIME_Timestamp exchange_timestamp; 107 108 /** 109 * Details about an encrypted contract, if any. 110 */ 111 struct TALER_EncryptedContract econtract; 112 113 /** 114 * Merge key for the purse. 115 */ 116 struct TALER_PurseMergePublicKeyP merge_pub; 117 118 /** 119 * Merge affirmation by the @e merge_pub. 120 */ 121 struct TALER_PurseMergeSignatureP merge_sig; 122 123 /** 124 * Signature of the client affiming this request. 125 */ 126 struct TALER_PurseContractSignatureP purse_sig; 127 128 /** 129 * Fundamental details about the purse. 130 */ 131 struct TEH_PurseDetails pd; 132 133 /** 134 * Hash of the @e payto_uri. 135 */ 136 struct TALER_NormalizedPaytoHashP h_payto; 137 138 /** 139 * KYC status of the operation. 140 */ 141 struct TALER_EXCHANGEDB_KycStatus kyc; 142 143 /** 144 * Minimum age for deposits into this purse. 145 */ 146 uint32_t min_age; 147 148 /** 149 * Flags for the operation. 150 */ 151 enum TALER_WalletAccountMergeFlags flags; 152 153 /** 154 * HTTP status code for @a response, or 0 155 */ 156 unsigned int http_status; 157 158 /** 159 * Do we lack an @e econtract? 160 */ 161 bool no_econtract; 162 163 /** 164 * Set to true if the purse_fee was not given in the REST request. 165 */ 166 bool no_purse_fee; 167 168 }; 169 170 /** 171 * Kept in a DLL. 172 */ 173 static struct ReservePurseContext *rpc_head; 174 175 /** 176 * Kept in a DLL. 177 */ 178 static struct ReservePurseContext *rpc_tail; 179 180 181 void 182 TEH_reserves_purse_cleanup () 183 { 184 struct ReservePurseContext *rpc; 185 186 while (NULL != (rpc = rpc_head)) 187 { 188 GNUNET_CONTAINER_DLL_remove (rpc_head, 189 rpc_tail, 190 rpc); 191 MHD_resume_connection (rpc->rc->connection); 192 } 193 } 194 195 196 /** 197 * Function called to iterate over KYC-relevant 198 * transaction amounts for a particular time range. 199 * Called within a database transaction, so must 200 * not start a new one. 201 * 202 * @param cls a `struct ReservePurseContext` 203 * @param limit maximum time-range for which events 204 * should be fetched (timestamp in the past) 205 * @param cb function to call on each event found, 206 * events must be returned in reverse chronological 207 * order 208 * @param cb_cls closure for @a cb 209 * @return transaction status 210 */ 211 static enum GNUNET_DB_QueryStatus 212 amount_iterator (void *cls, 213 struct GNUNET_TIME_Absolute limit, 214 TALER_EXCHANGEDB_KycAmountCallback cb, 215 void *cb_cls) 216 { 217 struct ReservePurseContext *rpc = cls; 218 enum GNUNET_DB_QueryStatus qs; 219 enum GNUNET_GenericReturnValue ret; 220 221 ret = cb (cb_cls, 222 &rpc->pd.target_amount, 223 GNUNET_TIME_absolute_get ()); 224 GNUNET_break (GNUNET_SYSERR != ret); 225 if (GNUNET_OK != ret) 226 return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; 227 qs = TEH_plugin->select_merge_amounts_for_kyc_check ( 228 TEH_plugin->cls, 229 &rpc->h_payto, 230 limit, 231 cb, 232 cb_cls); 233 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 234 "Got %d additional transactions for this merge and limit %llu\n", 235 qs, 236 (unsigned long long) limit.abs_value_us); 237 GNUNET_break (qs >= 0); 238 return qs; 239 } 240 241 242 /** 243 * Function called with the result of a legitimization 244 * check. 245 * 246 * @param cls closure 247 * @param lcr legitimization check result 248 */ 249 static void 250 reserve_purse_legi_cb ( 251 void *cls, 252 const struct TEH_LegitimizationCheckResult *lcr) 253 { 254 struct ReservePurseContext *rpc = cls; 255 256 rpc->lch = NULL; 257 rpc->http_status = lcr->http_status; 258 rpc->response = lcr->response; 259 rpc->kyc = lcr->kyc; 260 GNUNET_CONTAINER_DLL_remove (rpc_head, 261 rpc_tail, 262 rpc); 263 MHD_resume_connection (rpc->rc->connection); 264 TALER_MHD_daemon_trigger (); 265 } 266 267 268 /** 269 * Execute database transaction for /reserves/$PID/purse. Runs the transaction 270 * logic; IF it returns a non-error code, the transaction logic MUST NOT queue 271 * a MHD response. IF it returns an hard error, the transaction logic MUST 272 * queue a MHD response and set @a mhd_ret. IF it returns the soft error 273 * code, the function MAY be called again to retry and MUST not queue a MHD 274 * response. 275 * 276 * @param cls a `struct ReservePurseContext` 277 * @param connection MHD request context 278 * @param[out] mhd_ret set to MHD status on error 279 * @return transaction status 280 */ 281 static enum GNUNET_DB_QueryStatus 282 purse_transaction (void *cls, 283 struct MHD_Connection *connection, 284 MHD_RESULT *mhd_ret) 285 { 286 struct ReservePurseContext *rpc = cls; 287 enum GNUNET_DB_QueryStatus qs; 288 289 { 290 bool in_conflict = true; 291 292 /* 1) store purse */ 293 qs = TEH_plugin->insert_purse_request ( 294 TEH_plugin->cls, 295 &rpc->pd.purse_pub, 296 &rpc->merge_pub, 297 rpc->pd.purse_expiration, 298 &rpc->pd.h_contract_terms, 299 rpc->min_age, 300 rpc->flags, 301 &rpc->purse_fee, 302 &rpc->pd.target_amount, 303 &rpc->purse_sig, 304 &in_conflict); 305 if (qs < 0) 306 { 307 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 308 return qs; 309 GNUNET_break (0); 310 *mhd_ret = 311 TALER_MHD_reply_with_error ( 312 connection, 313 MHD_HTTP_INTERNAL_SERVER_ERROR, 314 TALER_EC_GENERIC_DB_STORE_FAILED, 315 "insert purse request"); 316 return GNUNET_DB_STATUS_HARD_ERROR; 317 } 318 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 319 return qs; 320 if (in_conflict) 321 { 322 struct TALER_PurseMergePublicKeyP merge_pub; 323 struct GNUNET_TIME_Timestamp purse_expiration; 324 struct TALER_PrivateContractHashP h_contract_terms; 325 struct TALER_Amount target_amount; 326 struct TALER_Amount balance; 327 struct TALER_PurseContractSignatureP purse_sig; 328 uint32_t min_age; 329 330 TEH_plugin->rollback (TEH_plugin->cls); 331 qs = TEH_plugin->get_purse_request ( 332 TEH_plugin->cls, 333 &rpc->pd.purse_pub, 334 &merge_pub, 335 &purse_expiration, 336 &h_contract_terms, 337 &min_age, 338 &target_amount, 339 &balance, 340 &purse_sig); 341 if (qs <= 0) 342 { 343 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); 344 GNUNET_break (0 != qs); 345 TALER_LOG_WARNING ("Failed to fetch purse information from database\n"); 346 *mhd_ret = TALER_MHD_reply_with_error ( 347 connection, 348 MHD_HTTP_INTERNAL_SERVER_ERROR, 349 TALER_EC_GENERIC_DB_FETCH_FAILED, 350 "select purse request"); 351 return GNUNET_DB_STATUS_HARD_ERROR; 352 } 353 *mhd_ret 354 = TALER_MHD_REPLY_JSON_PACK ( 355 connection, 356 MHD_HTTP_CONFLICT, 357 TALER_JSON_pack_ec ( 358 TALER_EC_EXCHANGE_RESERVES_PURSE_CREATE_CONFLICTING_META_DATA), 359 TALER_JSON_pack_amount ("amount", 360 &target_amount), 361 GNUNET_JSON_pack_uint64 ("min_age", 362 min_age), 363 GNUNET_JSON_pack_timestamp ("purse_expiration", 364 purse_expiration), 365 GNUNET_JSON_pack_data_auto ("purse_sig", 366 &purse_sig), 367 GNUNET_JSON_pack_data_auto ("h_contract_terms", 368 &h_contract_terms), 369 GNUNET_JSON_pack_data_auto ("merge_pub", 370 &merge_pub)); 371 return GNUNET_DB_STATUS_HARD_ERROR; 372 } 373 374 } 375 376 /* 2) create purse with reserve (and debit reserve for purse creation!) */ 377 { 378 bool in_conflict = true; 379 bool insufficient_funds = true; 380 bool no_reserve = true; 381 382 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 383 "Creating purse with flags %d\n", 384 rpc->flags); 385 qs = TEH_plugin->do_reserve_purse ( 386 TEH_plugin->cls, 387 &rpc->pd.purse_pub, 388 &rpc->merge_sig, 389 rpc->merge_timestamp, 390 &rpc->reserve_sig, 391 (TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA 392 == rpc->flags) 393 ? NULL 394 : &rpc->purse_fee, 395 &rpc->account_pub.reserve_pub, 396 &in_conflict, 397 &no_reserve, 398 &insufficient_funds); 399 if (qs < 0) 400 { 401 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 402 return qs; 403 TALER_LOG_WARNING ( 404 "Failed to store purse merge information in database\n"); 405 *mhd_ret = 406 TALER_MHD_reply_with_error ( 407 connection, 408 MHD_HTTP_INTERNAL_SERVER_ERROR, 409 TALER_EC_GENERIC_DB_STORE_FAILED, 410 "do reserve purse"); 411 return GNUNET_DB_STATUS_HARD_ERROR; 412 } 413 if (in_conflict) 414 { 415 /* same purse already merged into a different reserve!? */ 416 struct TALER_PurseContractPublicKeyP purse_pub; 417 struct TALER_PurseMergeSignatureP merge_sig; 418 struct GNUNET_TIME_Timestamp merge_timestamp; 419 char *partner_url; 420 struct TALER_ReservePublicKeyP reserve_pub; 421 bool refunded; 422 423 TEH_plugin->rollback (TEH_plugin->cls); 424 qs = TEH_plugin->select_purse_merge ( 425 TEH_plugin->cls, 426 &purse_pub, 427 &merge_sig, 428 &merge_timestamp, 429 &partner_url, 430 &reserve_pub, 431 &refunded); 432 if (qs <= 0) 433 { 434 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); 435 GNUNET_break (0 != qs); 436 TALER_LOG_WARNING ( 437 "Failed to fetch purse merge information from database\n"); 438 *mhd_ret = TALER_MHD_reply_with_error ( 439 connection, 440 MHD_HTTP_INTERNAL_SERVER_ERROR, 441 TALER_EC_GENERIC_DB_FETCH_FAILED, 442 "select purse merge"); 443 return GNUNET_DB_STATUS_HARD_ERROR; 444 } 445 if (refunded) 446 { 447 /* This is a bit of a strange case ... */ 448 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 449 "Purse was already refunded\n"); 450 *mhd_ret = TALER_MHD_reply_with_error ( 451 connection, 452 MHD_HTTP_GONE, 453 TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED, 454 NULL); 455 GNUNET_free (partner_url); 456 return GNUNET_DB_STATUS_HARD_ERROR; 457 } 458 *mhd_ret 459 = TALER_MHD_REPLY_JSON_PACK ( 460 connection, 461 MHD_HTTP_CONFLICT, 462 TALER_JSON_pack_ec ( 463 TALER_EC_EXCHANGE_RESERVES_PURSE_MERGE_CONFLICTING_META_DATA), 464 GNUNET_JSON_pack_string ("partner_url", 465 NULL == partner_url 466 ? TEH_base_url 467 : partner_url), 468 GNUNET_JSON_pack_timestamp ("merge_timestamp", 469 merge_timestamp), 470 GNUNET_JSON_pack_data_auto ("merge_sig", 471 &merge_sig), 472 GNUNET_JSON_pack_data_auto ("reserve_pub", 473 &reserve_pub)); 474 GNUNET_free (partner_url); 475 return GNUNET_DB_STATUS_HARD_ERROR; 476 } 477 if ( (no_reserve) && 478 ( (TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA 479 == rpc->flags) || 480 (! TALER_amount_is_zero (&rpc->purse_fee)) ) ) 481 { 482 *mhd_ret 483 = TALER_MHD_REPLY_JSON_PACK ( 484 connection, 485 MHD_HTTP_NOT_FOUND, 486 TALER_JSON_pack_ec ( 487 TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN)); 488 return GNUNET_DB_STATUS_HARD_ERROR; 489 } 490 if (insufficient_funds) 491 { 492 *mhd_ret 493 = TALER_MHD_REPLY_JSON_PACK ( 494 connection, 495 MHD_HTTP_CONFLICT, 496 TALER_JSON_pack_ec ( 497 TALER_EC_EXCHANGE_RESERVES_PURSE_CREATE_INSUFFICIENT_FUNDS)); 498 return GNUNET_DB_STATUS_HARD_ERROR; 499 } 500 } 501 /* 3) if present, persist contract */ 502 if (! rpc->no_econtract) 503 { 504 bool in_conflict = true; 505 506 qs = TEH_plugin->insert_contract (TEH_plugin->cls, 507 &rpc->pd.purse_pub, 508 &rpc->econtract, 509 &in_conflict); 510 if (qs < 0) 511 { 512 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 513 return qs; 514 TALER_LOG_WARNING ("Failed to store purse information in database\n"); 515 *mhd_ret = TALER_MHD_reply_with_error ( 516 connection, 517 MHD_HTTP_INTERNAL_SERVER_ERROR, 518 TALER_EC_GENERIC_DB_STORE_FAILED, 519 "purse purse contract"); 520 return GNUNET_DB_STATUS_HARD_ERROR; 521 } 522 if (in_conflict) 523 { 524 struct TALER_EncryptedContract econtract; 525 struct GNUNET_HashCode h_econtract; 526 527 qs = TEH_plugin->select_contract_by_purse ( 528 TEH_plugin->cls, 529 &rpc->pd.purse_pub, 530 &econtract); 531 if (qs <= 0) 532 { 533 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 534 return qs; 535 GNUNET_break (0 != qs); 536 TALER_LOG_WARNING ( 537 "Failed to store fetch contract information from database\n"); 538 *mhd_ret = TALER_MHD_reply_with_error ( 539 connection, 540 MHD_HTTP_INTERNAL_SERVER_ERROR, 541 TALER_EC_GENERIC_DB_FETCH_FAILED, 542 "select contract"); 543 return GNUNET_DB_STATUS_HARD_ERROR; 544 } 545 GNUNET_CRYPTO_hash (econtract.econtract, 546 econtract.econtract_size, 547 &h_econtract); 548 *mhd_ret 549 = TALER_MHD_REPLY_JSON_PACK ( 550 connection, 551 MHD_HTTP_CONFLICT, 552 TALER_JSON_pack_ec ( 553 TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA), 554 GNUNET_JSON_pack_data_auto ("h_econtract", 555 &h_econtract), 556 GNUNET_JSON_pack_data_auto ("econtract_sig", 557 &econtract.econtract_sig), 558 GNUNET_JSON_pack_data_auto ("contract_pub", 559 &econtract.contract_pub)); 560 return GNUNET_DB_STATUS_HARD_ERROR; 561 } 562 } 563 return qs; 564 } 565 566 567 /** 568 * Function to clean up our rh_ctx in @a rc 569 * 570 * @param[in,out] rc context to clean up 571 */ 572 static void 573 rpc_cleaner (struct TEH_RequestContext *rc) 574 { 575 struct ReservePurseContext *rpc = rc->rh_ctx; 576 577 if (NULL != rpc->lch) 578 { 579 TEH_legitimization_check_cancel (rpc->lch); 580 rpc->lch = NULL; 581 } 582 GNUNET_free (rpc->econtract.econtract); 583 GNUNET_free (rpc->payto_uri.normalized_payto); 584 GNUNET_free (rpc); 585 } 586 587 588 MHD_RESULT 589 TEH_handler_reserves_purse ( 590 struct TEH_RequestContext *rc, 591 const struct TALER_ReservePublicKeyP *reserve_pub, 592 const json_t *root) 593 { 594 struct ReservePurseContext *rpc = rc->rh_ctx; 595 struct MHD_Connection *connection = rc->connection; 596 597 if (NULL == rpc) 598 { 599 rpc = GNUNET_new (struct ReservePurseContext); 600 rc->rh_ctx = rpc; 601 rc->rh_cleaner = &rpc_cleaner; 602 rpc->rc = rc; 603 rpc->account_pub.reserve_pub = *reserve_pub; 604 rpc->exchange_timestamp 605 = GNUNET_TIME_timestamp_get (); 606 rpc->no_purse_fee = true; 607 608 609 { 610 struct GNUNET_JSON_Specification spec[] = { 611 TALER_JSON_spec_amount ("purse_value", 612 TEH_currency, 613 &rpc->pd.target_amount), 614 GNUNET_JSON_spec_uint32 ("min_age", 615 &rpc->min_age), 616 GNUNET_JSON_spec_mark_optional ( 617 TALER_JSON_spec_amount ("purse_fee", 618 TEH_currency, 619 &rpc->purse_fee), 620 &rpc->no_purse_fee), 621 GNUNET_JSON_spec_mark_optional ( 622 TALER_JSON_spec_econtract ("econtract", 623 &rpc->econtract), 624 &rpc->no_econtract), 625 GNUNET_JSON_spec_fixed_auto ("merge_pub", 626 &rpc->merge_pub), 627 GNUNET_JSON_spec_fixed_auto ("merge_sig", 628 &rpc->merge_sig), 629 GNUNET_JSON_spec_fixed_auto ("reserve_sig", 630 &rpc->reserve_sig), 631 GNUNET_JSON_spec_fixed_auto ("purse_pub", 632 &rpc->pd.purse_pub), 633 GNUNET_JSON_spec_fixed_auto ("purse_sig", 634 &rpc->purse_sig), 635 GNUNET_JSON_spec_fixed_auto ("h_contract_terms", 636 &rpc->pd.h_contract_terms), 637 GNUNET_JSON_spec_timestamp ("merge_timestamp", 638 &rpc->merge_timestamp), 639 GNUNET_JSON_spec_timestamp ("purse_expiration", 640 &rpc->pd.purse_expiration), 641 GNUNET_JSON_spec_end () 642 }; 643 644 { 645 enum GNUNET_GenericReturnValue res; 646 647 res = TALER_MHD_parse_json_data (connection, 648 root, 649 spec); 650 if (GNUNET_SYSERR == res) 651 { 652 GNUNET_break (0); 653 return MHD_NO; /* hard failure */ 654 } 655 if (GNUNET_NO == res) 656 { 657 GNUNET_break_op (0); 658 return MHD_YES; /* failure */ 659 } 660 } 661 } 662 663 rpc->payto_uri 664 = TALER_reserve_make_payto (TEH_base_url, 665 &rpc->account_pub.reserve_pub); 666 TALER_normalized_payto_hash (rpc->payto_uri, 667 &rpc->h_payto); 668 TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; 669 670 671 if (GNUNET_OK != 672 TALER_wallet_purse_merge_verify (rpc->payto_uri, 673 rpc->merge_timestamp, 674 &rpc->pd.purse_pub, 675 &rpc->merge_pub, 676 &rpc->merge_sig)) 677 { 678 MHD_RESULT ret; 679 680 GNUNET_break_op (0); 681 ret = TALER_MHD_reply_with_error ( 682 connection, 683 MHD_HTTP_FORBIDDEN, 684 TALER_EC_EXCHANGE_RESERVES_PURSE_MERGE_SIGNATURE_INVALID, 685 rpc->payto_uri.normalized_payto); 686 return ret; 687 } 688 GNUNET_assert (GNUNET_OK == 689 TALER_amount_set_zero (TEH_currency, 690 &rpc->deposit_total)); 691 if (GNUNET_TIME_timestamp_cmp (rpc->pd.purse_expiration, 692 <, 693 rpc->exchange_timestamp)) 694 { 695 GNUNET_break_op (0); 696 return TALER_MHD_reply_with_error ( 697 connection, 698 MHD_HTTP_BAD_REQUEST, 699 TALER_EC_EXCHANGE_RESERVES_PURSE_EXPIRATION_BEFORE_NOW, 700 NULL); 701 } 702 if (GNUNET_TIME_absolute_is_never ( 703 rpc->pd.purse_expiration.abs_time)) 704 { 705 GNUNET_break_op (0); 706 return TALER_MHD_reply_with_error ( 707 connection, 708 MHD_HTTP_BAD_REQUEST, 709 TALER_EC_EXCHANGE_RESERVES_PURSE_EXPIRATION_IS_NEVER, 710 NULL); 711 } 712 713 { 714 struct TEH_KeyStateHandle *keys; 715 const struct TEH_GlobalFee *gf; 716 717 keys = TEH_keys_get_state (); 718 if (NULL == keys) 719 { 720 GNUNET_break (0); 721 return TALER_MHD_reply_with_error ( 722 connection, 723 MHD_HTTP_INTERNAL_SERVER_ERROR, 724 TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, 725 NULL); 726 } 727 gf = TEH_keys_global_fee_by_time (keys, 728 rpc->exchange_timestamp); 729 if (NULL == gf) 730 { 731 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 732 "Cannot create purse: global fees not configured!\n"); 733 return TALER_MHD_reply_with_error ( 734 connection, 735 MHD_HTTP_INTERNAL_SERVER_ERROR, 736 TALER_EC_EXCHANGE_GENERIC_GLOBAL_FEES_MISSING, 737 NULL); 738 } 739 if (rpc->no_purse_fee) 740 { 741 rpc->flags = TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA; 742 GNUNET_assert (GNUNET_OK == 743 TALER_amount_set_zero (TEH_currency, 744 &rpc->purse_fee)); 745 } 746 else 747 { 748 rpc->flags = TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE; 749 if (-1 == 750 TALER_amount_cmp (&rpc->purse_fee, 751 &gf->fees.purse)) 752 { 753 /* rpc->purse_fee is below gf.fees.purse! */ 754 GNUNET_break_op (0); 755 return TALER_MHD_reply_with_error ( 756 connection, 757 MHD_HTTP_BAD_REQUEST, 758 TALER_EC_EXCHANGE_RESERVES_PURSE_FEE_TOO_LOW, 759 TALER_amount2s (&gf->fees.purse)); 760 } 761 } 762 } 763 764 TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; 765 if (GNUNET_OK != 766 TALER_wallet_purse_create_verify ( 767 rpc->pd.purse_expiration, 768 &rpc->pd.h_contract_terms, 769 &rpc->merge_pub, 770 rpc->min_age, 771 &rpc->pd.target_amount, 772 &rpc->pd.purse_pub, 773 &rpc->purse_sig)) 774 { 775 GNUNET_break_op (0); 776 return TALER_MHD_reply_with_error ( 777 connection, 778 MHD_HTTP_FORBIDDEN, 779 TALER_EC_EXCHANGE_PURSE_CREATE_SIGNATURE_INVALID, 780 NULL); 781 } 782 if (GNUNET_OK != 783 TALER_wallet_account_merge_verify ( 784 rpc->merge_timestamp, 785 &rpc->pd.purse_pub, 786 rpc->pd.purse_expiration, 787 &rpc->pd.h_contract_terms, 788 &rpc->pd.target_amount, 789 &rpc->purse_fee, 790 rpc->min_age, 791 rpc->flags, 792 &rpc->account_pub.reserve_pub, 793 &rpc->reserve_sig)) 794 { 795 GNUNET_break_op (0); 796 return TALER_MHD_reply_with_error ( 797 connection, 798 MHD_HTTP_FORBIDDEN, 799 TALER_EC_EXCHANGE_RESERVES_RESERVE_MERGE_SIGNATURE_INVALID, 800 NULL); 801 } 802 if ( (! rpc->no_econtract) && 803 (GNUNET_OK != 804 TALER_wallet_econtract_upload_verify ( 805 rpc->econtract.econtract, 806 rpc->econtract.econtract_size, 807 &rpc->econtract.contract_pub, 808 &rpc->pd.purse_pub, 809 &rpc->econtract.econtract_sig)) 810 ) 811 { 812 TALER_LOG_WARNING ("Invalid signature on /reserves/$PID/purse request\n"); 813 return TALER_MHD_reply_with_error (connection, 814 MHD_HTTP_FORBIDDEN, 815 TALER_EC_EXCHANGE_PURSE_ECONTRACT_SIGNATURE_INVALID, 816 NULL); 817 } 818 { 819 struct TALER_FullPayto fake_full_payto; 820 821 GNUNET_asprintf (&fake_full_payto.full_payto, 822 "%s?receiver-name=wallet", 823 rpc->payto_uri.normalized_payto); 824 rpc->lch = TEH_legitimization_check ( 825 &rpc->rc->async_scope_id, 826 TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE, 827 fake_full_payto, 828 &rpc->h_payto, 829 &rpc->account_pub, 830 &amount_iterator, 831 rpc, 832 &reserve_purse_legi_cb, 833 rpc); 834 GNUNET_free (fake_full_payto.full_payto); 835 } 836 GNUNET_assert (NULL != rpc->lch); 837 MHD_suspend_connection (rc->connection); 838 GNUNET_CONTAINER_DLL_insert (rpc_head, 839 rpc_tail, 840 rpc); 841 return MHD_YES; 842 } 843 if (NULL != rpc->response) 844 return MHD_queue_response (connection, 845 rpc->http_status, 846 rpc->response); 847 if (! rpc->kyc.ok) 848 { 849 if (0 == rpc->kyc.requirement_row) 850 { 851 GNUNET_break (0); 852 return TALER_MHD_reply_with_error ( 853 connection, 854 MHD_HTTP_INTERNAL_SERVER_ERROR, 855 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 856 "requirement row not set"); 857 } 858 return TEH_RESPONSE_reply_kyc_required ( 859 connection, 860 &rpc->h_payto, 861 &rpc->kyc, 862 false); 863 } 864 865 { 866 MHD_RESULT mhd_ret; 867 868 if (GNUNET_OK != 869 TEH_DB_run_transaction (connection, 870 "execute reserve purse", 871 TEH_MT_REQUEST_RESERVE_PURSE, 872 &mhd_ret, 873 &purse_transaction, 874 rpc)) 875 { 876 return mhd_ret; 877 } 878 } 879 880 /* generate regular response */ 881 { 882 MHD_RESULT res; 883 884 res = TEH_RESPONSE_reply_purse_created ( 885 connection, 886 rpc->exchange_timestamp, 887 &rpc->deposit_total, 888 &rpc->pd); 889 return res; 890 } 891 } 892 893 894 /* end of taler-exchange-httpd_reserves_purse.c */