taler-exchange-httpd_post-purses-PURSE_PUB-merge.c (23677B)
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_post-purses-PURSE_PUB-merge.c 18 * @brief Handle /purses/$PID/merge requests; parses the POST and JSON and 19 * verifies the reserve signature before handing things off 20 * to the database. 21 * @author Christian Grothoff 22 */ 23 #include "platform.h" /* UNNECESSARY? */ 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_dbevents.h" /* UNNECESSARY? */ 30 #include "taler/taler_json_lib.h" 31 #include "taler/taler_kyclogic_lib.h" 32 #include "taler/taler_mhd_lib.h" 33 #include "taler-exchange-httpd_common_kyc.h" 34 #include "taler-exchange-httpd_post-purses-PURSE_PUB-merge.h" 35 #include "taler-exchange-httpd_responses.h" 36 #include "exchangedb_lib.h" 37 #include "taler-exchange-httpd_get-keys.h" 38 #include "exchange-database/select_purse_merge.h" 39 #include "exchange-database/do_purse_merge.h" 40 #include "exchange-database/event_notify.h" 41 #include "exchange-database/get_purse_request.h" 42 #include "exchange-database/select_merge_amounts_for_kyc_check.h" 43 44 45 /** 46 * Closure for #merge_transaction. 47 */ 48 struct PurseMergeContext 49 { 50 51 /** 52 * Kept in a DLL. 53 */ 54 struct PurseMergeContext *next; 55 56 /** 57 * Kept in a DLL. 58 */ 59 struct PurseMergeContext *prev; 60 61 /** 62 * Our request. 63 */ 64 struct TEH_RequestContext *rc; 65 66 /** 67 * Handle for the legitimization check. 68 */ 69 struct TEH_LegitimizationCheckHandle *lch; 70 71 /** 72 * Fees that apply to this operation. 73 */ 74 const struct TALER_WireFeeSet *wf; 75 76 /** 77 * Base URL of the exchange provider hosting the reserve. 78 */ 79 char *provider_url; 80 81 /** 82 * URI of the account the purse is to be merged into. 83 * Must be of the form 'payto://taler-reserve/$EXCHANGE_URL/RESERVE_PUB'. 84 */ 85 struct TALER_NormalizedPayto payto_uri; 86 87 /** 88 * Response to return, if set. 89 */ 90 struct MHD_Response *response; 91 92 /** 93 * Public key of the purse we are creating. 94 */ 95 struct TALER_PurseContractPublicKeyP purse_pub; 96 97 /** 98 * Total amount to be put into the purse. 99 */ 100 struct TALER_Amount target_amount; 101 102 /** 103 * Current amount in the purse. 104 */ 105 struct TALER_Amount balance; 106 107 /** 108 * When should the purse expire. 109 */ 110 struct GNUNET_TIME_Timestamp purse_expiration; 111 112 /** 113 * When the client signed the merge. 114 */ 115 struct GNUNET_TIME_Timestamp merge_timestamp; 116 117 /** 118 * Our current time. 119 */ 120 struct GNUNET_TIME_Timestamp exchange_timestamp; 121 122 /** 123 * Merge key for the purse. 124 */ 125 struct TALER_PurseMergePublicKeyP merge_pub; 126 127 /** 128 * Signature of the reservce affiming this request. 129 */ 130 struct TALER_ReserveSignatureP reserve_sig; 131 132 /** 133 * Signature of the client affiming the merge. 134 */ 135 struct TALER_PurseMergeSignatureP merge_sig; 136 137 /** 138 * Public key of the reserve (account), as extracted from @e payto_uri. 139 */ 140 union TALER_AccountPublicKeyP account_pub; 141 142 /** 143 * Hash of the contract terms of the purse. 144 */ 145 struct TALER_PrivateContractHashP h_contract_terms; 146 147 /** 148 * Hash of the @e payto_uri. 149 */ 150 struct TALER_NormalizedPaytoHashP h_payto; 151 152 /** 153 * KYC status of the operation. 154 */ 155 struct TALER_EXCHANGEDB_KycStatus kyc; 156 157 /** 158 * HTTP status to return with @e response, or 0. 159 */ 160 unsigned int http_status; 161 162 /** 163 * Minimum age for deposits into this purse. 164 */ 165 uint32_t min_age; 166 167 /** 168 * Set to true if this request was suspended. 169 */ 170 bool suspended; 171 }; 172 173 174 /** 175 * Kept in a DLL. 176 */ 177 static struct PurseMergeContext *pmc_head; 178 179 /** 180 * Kept in a DLL. 181 */ 182 static struct PurseMergeContext *pmc_tail; 183 184 185 void 186 TEH_purses_merge_cleanup () 187 { 188 struct PurseMergeContext *pmc; 189 190 while (NULL != (pmc = pmc_head)) 191 { 192 GNUNET_CONTAINER_DLL_remove (pmc_head, 193 pmc_tail, 194 pmc); 195 MHD_resume_connection (pmc->rc->connection); 196 } 197 } 198 199 200 /** 201 * Function called with the result of a legitimization 202 * check. 203 * 204 * @param cls closure 205 * @param lcr legitimization check result 206 */ 207 static void 208 legi_result_cb ( 209 void *cls, 210 const struct TEH_LegitimizationCheckResult *lcr) 211 { 212 struct PurseMergeContext *pmc = cls; 213 214 pmc->lch = NULL; 215 MHD_resume_connection (pmc->rc->connection); 216 GNUNET_CONTAINER_DLL_remove (pmc_head, 217 pmc_tail, 218 pmc); 219 TALER_MHD_daemon_trigger (); 220 if (NULL != lcr->response) 221 { 222 pmc->response = lcr->response; 223 pmc->http_status = lcr->http_status; 224 return; 225 } 226 pmc->kyc = lcr->kyc; 227 } 228 229 230 /** 231 * Send confirmation of purse creation success to client. 232 * 233 * @param pmc details about the request that succeeded 234 * @return MHD result code 235 */ 236 static enum MHD_Result 237 reply_merge_success (const struct PurseMergeContext *pmc) 238 { 239 struct MHD_Connection *connection = pmc->rc->connection; 240 struct TALER_ExchangePublicKeyP pub; 241 struct TALER_ExchangeSignatureP sig; 242 enum TALER_ErrorCode ec; 243 struct TALER_Amount merge_amount; 244 245 if (0 < 246 TALER_amount_cmp (&pmc->balance, 247 &pmc->target_amount)) 248 { 249 GNUNET_break (0); 250 return TALER_MHD_REPLY_JSON_PACK ( 251 connection, 252 MHD_HTTP_INTERNAL_SERVER_ERROR, 253 TALER_JSON_pack_amount ("balance", 254 &pmc->balance), 255 TALER_JSON_pack_amount ("target_amount", 256 &pmc->target_amount)); 257 } 258 #pragma GCC diagnostic push 259 #pragma GCC diagnostic ignored "-Wduplicated-branches" 260 if ( (NULL == pmc->provider_url) || 261 (0 == strcmp (pmc->provider_url, 262 TEH_base_url)) ) 263 { 264 /* wad fee is always zero if we stay at our own exchange */ 265 merge_amount = pmc->target_amount; 266 } 267 else 268 { 269 #if WAD_NOT_IMPLEMENTED /* #7271 */ 270 /* FIXME: figure out partner, lookup wad fee by partner! #7271 */ 271 if (0 > 272 TALER_amount_subtract (&merge_amount, 273 &pmc->target_amount, 274 &wad_fee)) 275 { 276 GNUNET_assert (GNUNET_OK == 277 TALER_amount_set_zero (TEH_currency, 278 &merge_amount)); 279 } 280 #else 281 merge_amount = pmc->target_amount; 282 #endif 283 } 284 #pragma GCC diagnostic pop 285 if (TALER_EC_NONE != 286 (ec = TALER_exchange_online_purse_merged_sign ( 287 &TEH_keys_exchange_sign_, 288 pmc->exchange_timestamp, 289 pmc->purse_expiration, 290 &merge_amount, 291 &pmc->purse_pub, 292 &pmc->h_contract_terms, 293 &pmc->account_pub.reserve_pub, 294 (NULL != pmc->provider_url) 295 ? pmc->provider_url 296 : TEH_base_url, 297 &pub, 298 &sig))) 299 { 300 GNUNET_break (0); 301 return TALER_MHD_reply_with_ec (connection, 302 ec, 303 NULL); 304 } 305 return TALER_MHD_REPLY_JSON_PACK ( 306 connection, 307 MHD_HTTP_OK, 308 TALER_JSON_pack_amount ("merge_amount", 309 &merge_amount), 310 GNUNET_JSON_pack_timestamp ("exchange_timestamp", 311 pmc->exchange_timestamp), 312 GNUNET_JSON_pack_data_auto ("exchange_sig", 313 &sig), 314 GNUNET_JSON_pack_data_auto ("exchange_pub", 315 &pub)); 316 } 317 318 319 /** 320 * Function called to iterate over KYC-relevant 321 * transaction amounts for a particular time range. 322 * Called within a database transaction, so must 323 * not start a new one. 324 * 325 * @param cls a `struct PurseMergeContext` 326 * @param limit maximum time-range for which events 327 * should be fetched (timestamp in the past) 328 * @param cb function to call on each event found, 329 * events must be returned in reverse chronological 330 * order 331 * @param cb_cls closure for @a cb 332 * @return transaction status 333 */ 334 static enum GNUNET_DB_QueryStatus 335 amount_iterator (void *cls, 336 struct GNUNET_TIME_Absolute limit, 337 TALER_KYCLOGIC_KycAmountCallback cb, 338 void *cb_cls) 339 { 340 struct PurseMergeContext *pmc = cls; 341 enum GNUNET_GenericReturnValue ret; 342 enum GNUNET_DB_QueryStatus qs; 343 344 ret = cb (cb_cls, 345 &pmc->target_amount, 346 GNUNET_TIME_absolute_get ()); 347 GNUNET_break (GNUNET_SYSERR != ret); 348 if (GNUNET_OK != ret) 349 return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; 350 qs = TALER_EXCHANGEDB_select_merge_amounts_for_kyc_check ( 351 TEH_pg, 352 &pmc->h_payto, 353 limit, 354 cb, 355 cb_cls); 356 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 357 "Got %d additional transactions for this merge and limit %llu\n", 358 qs, 359 (unsigned long long) limit.abs_value_us); 360 GNUNET_break (qs >= 0); 361 return qs; 362 } 363 364 365 /** 366 * Execute database transaction for /purses/$PID/merge. Runs the transaction 367 * logic; IF it returns a non-error code, the transaction logic MUST NOT queue 368 * a MHD response. IF it returns an hard error, the transaction logic MUST 369 * queue a MHD response and set @a mhd_ret. IF it returns the soft error 370 * code, the function MAY be called again to retry and MUST not queue a MHD 371 * response. 372 * 373 * @param cls a `struct PurseMergeContext` 374 * @param connection MHD request context 375 * @param[out] mhd_ret set to MHD status on error 376 * @return transaction status 377 */ 378 static enum GNUNET_DB_QueryStatus 379 merge_transaction (void *cls, 380 struct MHD_Connection *connection, 381 enum MHD_Result *mhd_ret) 382 { 383 struct PurseMergeContext *pmc = cls; 384 enum GNUNET_DB_QueryStatus qs; 385 bool in_conflict = true; 386 bool no_balance = true; 387 bool no_partner = true; 388 389 qs = TALER_EXCHANGEDB_do_purse_merge ( 390 TEH_pg, 391 &pmc->purse_pub, 392 &pmc->merge_sig, 393 pmc->merge_timestamp, 394 &pmc->reserve_sig, 395 pmc->provider_url, 396 &pmc->account_pub.reserve_pub, 397 &no_partner, 398 &no_balance, 399 &in_conflict); 400 if (qs < 0) 401 { 402 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 403 return qs; 404 GNUNET_break (0); 405 *mhd_ret = 406 TALER_MHD_reply_with_error (connection, 407 MHD_HTTP_INTERNAL_SERVER_ERROR, 408 TALER_EC_GENERIC_DB_STORE_FAILED, 409 "purse merge"); 410 return qs; 411 } 412 if (no_partner) 413 { 414 *mhd_ret = 415 TALER_MHD_reply_with_error (connection, 416 MHD_HTTP_NOT_FOUND, 417 TALER_EC_EXCHANGE_MERGE_PURSE_PARTNER_UNKNOWN, 418 pmc->provider_url); 419 return GNUNET_DB_STATUS_HARD_ERROR; 420 } 421 if (no_balance) 422 { 423 *mhd_ret = 424 TALER_MHD_reply_with_error (connection, 425 MHD_HTTP_PAYMENT_REQUIRED, 426 TALER_EC_EXCHANGE_PURSE_NOT_FULL, 427 NULL); 428 return GNUNET_DB_STATUS_HARD_ERROR; 429 } 430 if (in_conflict) 431 { 432 struct TALER_PurseMergeSignatureP merge_sig; 433 struct GNUNET_TIME_Timestamp merge_timestamp; 434 char *partner_url = NULL; 435 struct TALER_ReservePublicKeyP reserve_pub; 436 bool refunded; 437 438 qs = TALER_TALER_EXCHANGEDB_select_purse_merge (TEH_pg, 439 &pmc->purse_pub, 440 &merge_sig, 441 &merge_timestamp, 442 &partner_url, 443 &reserve_pub, 444 &refunded); 445 if (qs <= 0) 446 { 447 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 448 return qs; 449 TALER_LOG_WARNING ( 450 "Failed to fetch merge purse information from database\n"); 451 *mhd_ret = 452 TALER_MHD_reply_with_error (connection, 453 MHD_HTTP_INTERNAL_SERVER_ERROR, 454 TALER_EC_GENERIC_DB_FETCH_FAILED, 455 "select purse merge"); 456 return qs; 457 } 458 if (refunded) 459 { 460 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 461 "Purse was already refunded\n"); 462 *mhd_ret = TALER_MHD_reply_with_error (connection, 463 MHD_HTTP_GONE, 464 TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED, 465 NULL); 466 GNUNET_free (partner_url); 467 return GNUNET_DB_STATUS_HARD_ERROR; 468 } 469 if (0 != 470 GNUNET_memcmp (&merge_sig, 471 &pmc->merge_sig)) 472 { 473 *mhd_ret = TALER_MHD_REPLY_JSON_PACK ( 474 connection, 475 MHD_HTTP_CONFLICT, 476 GNUNET_JSON_pack_timestamp ("merge_timestamp", 477 merge_timestamp), 478 GNUNET_JSON_pack_data_auto ("merge_sig", 479 &merge_sig), 480 GNUNET_JSON_pack_allow_null ( 481 GNUNET_JSON_pack_string ("partner_url", 482 partner_url)), 483 GNUNET_JSON_pack_data_auto ("reserve_pub", 484 &reserve_pub)); 485 GNUNET_free (partner_url); 486 return GNUNET_DB_STATUS_HARD_ERROR; 487 } 488 /* idempotent! */ 489 *mhd_ret = reply_merge_success (pmc); 490 GNUNET_free (partner_url); 491 return GNUNET_DB_STATUS_HARD_ERROR; 492 } 493 494 return qs; 495 } 496 497 498 /** 499 * Purse-merge-specific cleanup routine. Function called 500 * upon completion of the request that should 501 * clean up @a rh_ctx. Can be NULL. 502 * 503 * @param rc request context to clean up 504 */ 505 static void 506 clean_purse_merge_rc (struct TEH_RequestContext *rc) 507 { 508 struct PurseMergeContext *pmc = rc->rh_ctx; 509 510 if (NULL != pmc->lch) 511 { 512 TEH_legitimization_check_cancel (pmc->lch); 513 pmc->lch = NULL; 514 } 515 GNUNET_free (pmc->provider_url); 516 GNUNET_free (pmc); 517 } 518 519 520 enum MHD_Result 521 TEH_handler_purses_merge ( 522 struct TEH_RequestContext *rc, 523 const struct TALER_PurseContractPublicKeyP *purse_pub, 524 const json_t *root) 525 { 526 struct PurseMergeContext *pmc = rc->rh_ctx; 527 528 if (NULL == pmc) 529 { 530 pmc = GNUNET_new (struct PurseMergeContext); 531 rc->rh_ctx = pmc; 532 rc->rh_cleaner = &clean_purse_merge_rc; 533 pmc->rc = rc; 534 pmc->purse_pub = *purse_pub; 535 pmc->exchange_timestamp 536 = GNUNET_TIME_timestamp_get (); 537 538 { 539 struct GNUNET_JSON_Specification spec[] = { 540 TALER_JSON_spec_normalized_payto_uri ("payto_uri", 541 &pmc->payto_uri), 542 GNUNET_JSON_spec_fixed_auto ("reserve_sig", 543 &pmc->reserve_sig), 544 GNUNET_JSON_spec_fixed_auto ("merge_sig", 545 &pmc->merge_sig), 546 GNUNET_JSON_spec_timestamp ("merge_timestamp", 547 &pmc->merge_timestamp), 548 GNUNET_JSON_spec_end () 549 }; 550 enum GNUNET_GenericReturnValue res; 551 552 res = TALER_MHD_parse_json_data (rc->connection, 553 root, 554 spec); 555 if (GNUNET_SYSERR == res) 556 { 557 GNUNET_break (0); 558 return MHD_NO; /* hard failure */ 559 } 560 if (GNUNET_NO == res) 561 { 562 GNUNET_break_op (0); 563 return MHD_YES; /* failure */ 564 } 565 } 566 567 { 568 struct TALER_PurseContractSignatureP purse_sig; 569 enum GNUNET_DB_QueryStatus qs; 570 571 /* Fetch purse details */ 572 qs = TALER_EXCHANGEDB_get_purse_request ( 573 TEH_pg, 574 &pmc->purse_pub, 575 &pmc->merge_pub, 576 &pmc->purse_expiration, 577 &pmc->h_contract_terms, 578 &pmc->min_age, 579 &pmc->target_amount, 580 &pmc->balance, 581 &purse_sig); 582 switch (qs) 583 { 584 case GNUNET_DB_STATUS_HARD_ERROR: 585 GNUNET_break (0); 586 return TALER_MHD_reply_with_error ( 587 rc->connection, 588 MHD_HTTP_INTERNAL_SERVER_ERROR, 589 TALER_EC_GENERIC_DB_FETCH_FAILED, 590 "select purse request"); 591 case GNUNET_DB_STATUS_SOFT_ERROR: 592 GNUNET_break (0); 593 return TALER_MHD_reply_with_error ( 594 rc->connection, 595 MHD_HTTP_INTERNAL_SERVER_ERROR, 596 TALER_EC_GENERIC_DB_FETCH_FAILED, 597 "select purse request"); 598 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 599 return TALER_MHD_reply_with_error ( 600 rc->connection, 601 MHD_HTTP_NOT_FOUND, 602 TALER_EC_EXCHANGE_GENERIC_PURSE_UNKNOWN, 603 NULL); 604 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 605 /* continued below */ 606 break; 607 } 608 } 609 610 /* check signatures */ 611 if (GNUNET_OK != 612 TALER_wallet_purse_merge_verify ( 613 pmc->payto_uri, 614 pmc->merge_timestamp, 615 &pmc->purse_pub, 616 &pmc->merge_pub, 617 &pmc->merge_sig)) 618 { 619 GNUNET_break_op (0); 620 return TALER_MHD_reply_with_error ( 621 rc->connection, 622 MHD_HTTP_FORBIDDEN, 623 TALER_EC_EXCHANGE_PURSE_MERGE_INVALID_MERGE_SIGNATURE, 624 NULL); 625 } 626 627 /* parse 'payto_uri' into pmc->account_pub and provider_url */ 628 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 629 "Received payto: `%s'\n", 630 pmc->payto_uri.normalized_payto); 631 if ( (0 != strncmp (pmc->payto_uri.normalized_payto, 632 "payto://taler-reserve/", 633 strlen ("payto://taler-reserve/"))) && 634 (0 != strncmp (pmc->payto_uri.normalized_payto, 635 "payto://taler-reserve-http/", 636 strlen ("payto://taler-reserve-http/"))) ) 637 { 638 GNUNET_break_op (0); 639 return TALER_MHD_reply_with_error ( 640 rc->connection, 641 MHD_HTTP_BAD_REQUEST, 642 TALER_EC_GENERIC_PARAMETER_MALFORMED, 643 "payto_uri"); 644 } 645 646 { 647 bool http; 648 const char *host; 649 const char *slash; 650 651 http = (0 == strncmp (pmc->payto_uri.normalized_payto, 652 "payto://taler-reserve-http/", 653 strlen ("payto://taler-reserve-http/"))); 654 host = &pmc->payto_uri.normalized_payto[http 655 ? strlen ("payto://taler-reserve-http/") 656 : strlen ("payto://taler-reserve/")]; 657 slash = strchr (host, 658 '/'); 659 if (NULL == slash) 660 { 661 GNUNET_break_op (0); 662 return TALER_MHD_reply_with_error ( 663 rc->connection, 664 MHD_HTTP_BAD_REQUEST, 665 TALER_EC_GENERIC_PARAMETER_MALFORMED, 666 "payto_uri"); 667 } 668 GNUNET_asprintf (&pmc->provider_url, 669 "%s://%.*s/", 670 http ? "http" : "https", 671 (int) (slash - host), 672 host); 673 slash++; 674 if (GNUNET_OK != 675 GNUNET_STRINGS_string_to_data ( 676 slash, 677 strlen (slash), 678 &pmc->account_pub.reserve_pub, 679 sizeof (pmc->account_pub.reserve_pub))) 680 { 681 GNUNET_break_op (0); 682 return TALER_MHD_reply_with_error ( 683 rc->connection, 684 MHD_HTTP_BAD_REQUEST, 685 TALER_EC_GENERIC_PARAMETER_MALFORMED, 686 "payto_uri"); 687 } 688 } 689 TALER_normalized_payto_hash (pmc->payto_uri, 690 &pmc->h_payto); 691 if (0 == strcmp (pmc->provider_url, 692 TEH_base_url)) 693 { 694 /* we use NULL to represent 'self' as the provider */ 695 GNUNET_free (pmc->provider_url); 696 } 697 else 698 { 699 char *method = GNUNET_strdup ("FIXME-WAD #7271"); 700 701 /* FIXME-#7271: lookup wire method by pmc.provider_url! */ 702 pmc->wf = TEH_wire_fees_by_time (pmc->exchange_timestamp, 703 method); 704 if (NULL == pmc->wf) 705 { 706 enum MHD_Result res; 707 708 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 709 "Cannot merge purse: wire fees not configured!\n"); 710 res = TALER_MHD_reply_with_error ( 711 rc->connection, 712 MHD_HTTP_INTERNAL_SERVER_ERROR, 713 TALER_EC_EXCHANGE_GENERIC_WIRE_FEES_MISSING, 714 method); 715 GNUNET_free (method); 716 return res; 717 } 718 GNUNET_free (method); 719 } 720 721 { 722 struct TALER_Amount zero_purse_fee; 723 724 GNUNET_assert (GNUNET_OK == 725 TALER_amount_set_zero ( 726 pmc->target_amount.currency, 727 &zero_purse_fee)); 728 if (GNUNET_OK != 729 TALER_wallet_account_merge_verify ( 730 pmc->merge_timestamp, 731 &pmc->purse_pub, 732 pmc->purse_expiration, 733 &pmc->h_contract_terms, 734 &pmc->target_amount, 735 &zero_purse_fee, 736 pmc->min_age, 737 TALER_WAMF_MODE_MERGE_FULLY_PAID_PURSE, 738 &pmc->account_pub.reserve_pub, 739 &pmc->reserve_sig)) 740 { 741 GNUNET_break_op (0); 742 return TALER_MHD_reply_with_error ( 743 rc->connection, 744 MHD_HTTP_FORBIDDEN, 745 TALER_EC_EXCHANGE_PURSE_MERGE_INVALID_RESERVE_SIGNATURE, 746 NULL); 747 } 748 } 749 { 750 struct TALER_FullPayto fake_full_payto; 751 752 GNUNET_asprintf (&fake_full_payto.full_payto, 753 "%s?receiver-name=wallet", 754 pmc->payto_uri.normalized_payto); 755 pmc->lch = TEH_legitimization_check ( 756 &rc->async_scope_id, 757 TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE, 758 fake_full_payto, 759 &pmc->h_payto, 760 &pmc->account_pub, 761 &amount_iterator, 762 pmc, 763 &legi_result_cb, 764 pmc); 765 GNUNET_free (fake_full_payto.full_payto); 766 } 767 GNUNET_assert (NULL != pmc->lch); 768 MHD_suspend_connection (rc->connection); 769 GNUNET_CONTAINER_DLL_insert (pmc_head, 770 pmc_tail, 771 pmc); 772 return MHD_YES; 773 } 774 if (NULL != pmc->response) 775 { 776 return MHD_queue_response (rc->connection, 777 pmc->http_status, 778 pmc->response); 779 } 780 if (! pmc->kyc.ok) 781 return TEH_RESPONSE_reply_kyc_required ( 782 rc->connection, 783 &pmc->h_payto, 784 &pmc->kyc, 785 false); 786 787 /* execute merge transaction */ 788 { 789 enum MHD_Result mhd_ret; 790 791 if (GNUNET_OK != 792 TEH_DB_run_transaction (rc->connection, 793 "execute purse merge", 794 TEH_MT_REQUEST_PURSE_MERGE, 795 &mhd_ret, 796 &merge_transaction, 797 pmc)) 798 { 799 return mhd_ret; 800 } 801 } 802 803 { 804 struct TALER_EXCHANGEDB_PurseEventP rep = { 805 .header.size = htons (sizeof (rep)), 806 .header.type = htons (TALER_DBEVENT_EXCHANGE_PURSE_MERGED), 807 .purse_pub = pmc->purse_pub 808 }; 809 810 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 811 "Notifying about purse merge\n"); 812 TALER_EXCHANGEDB_event_notify (TEH_pg, 813 &rep.header, 814 NULL, 815 0); 816 } 817 818 /* generate regular response */ 819 return reply_merge_success (pmc); 820 } 821 822 823 /* end of taler-exchange-httpd_purses_merge.c */