taler-exchange-httpd_post-purses-PURSE_PUB-merge.c (23542B)
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 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 if ( (NULL == pmc->provider_url) || 259 (0 == strcmp (pmc->provider_url, 260 TEH_base_url)) ) 261 { 262 /* wad fee is always zero if we stay at our own exchange */ 263 merge_amount = pmc->target_amount; 264 } 265 else 266 { 267 #if WAD_NOT_IMPLEMENTED /* #7271 */ 268 /* FIXME: figure out partner, lookup wad fee by partner! #7271 */ 269 if (0 > 270 TALER_amount_subtract (&merge_amount, 271 &pmc->target_amount, 272 &wad_fee)) 273 { 274 GNUNET_assert (GNUNET_OK == 275 TALER_amount_set_zero (TEH_currency, 276 &merge_amount)); 277 } 278 #else 279 merge_amount = pmc->target_amount; 280 #endif 281 } 282 if (TALER_EC_NONE != 283 (ec = TALER_exchange_online_purse_merged_sign ( 284 &TEH_keys_exchange_sign_, 285 pmc->exchange_timestamp, 286 pmc->purse_expiration, 287 &merge_amount, 288 &pmc->purse_pub, 289 &pmc->h_contract_terms, 290 &pmc->account_pub.reserve_pub, 291 (NULL != pmc->provider_url) 292 ? pmc->provider_url 293 : TEH_base_url, 294 &pub, 295 &sig))) 296 { 297 GNUNET_break (0); 298 return TALER_MHD_reply_with_ec (connection, 299 ec, 300 NULL); 301 } 302 return TALER_MHD_REPLY_JSON_PACK ( 303 connection, 304 MHD_HTTP_OK, 305 TALER_JSON_pack_amount ("merge_amount", 306 &merge_amount), 307 GNUNET_JSON_pack_timestamp ("exchange_timestamp", 308 pmc->exchange_timestamp), 309 GNUNET_JSON_pack_data_auto ("exchange_sig", 310 &sig), 311 GNUNET_JSON_pack_data_auto ("exchange_pub", 312 &pub)); 313 } 314 315 316 /** 317 * Function called to iterate over KYC-relevant 318 * transaction amounts for a particular time range. 319 * Called within a database transaction, so must 320 * not start a new one. 321 * 322 * @param cls a `struct PurseMergeContext` 323 * @param limit maximum time-range for which events 324 * should be fetched (timestamp in the past) 325 * @param cb function to call on each event found, 326 * events must be returned in reverse chronological 327 * order 328 * @param cb_cls closure for @a cb 329 * @return transaction status 330 */ 331 static enum GNUNET_DB_QueryStatus 332 amount_iterator (void *cls, 333 struct GNUNET_TIME_Absolute limit, 334 TALER_KYCLOGIC_KycAmountCallback cb, 335 void *cb_cls) 336 { 337 struct PurseMergeContext *pmc = cls; 338 enum GNUNET_GenericReturnValue ret; 339 enum GNUNET_DB_QueryStatus qs; 340 341 ret = cb (cb_cls, 342 &pmc->target_amount, 343 GNUNET_TIME_absolute_get ()); 344 GNUNET_break (GNUNET_SYSERR != ret); 345 if (GNUNET_OK != ret) 346 return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; 347 qs = TALER_EXCHANGEDB_select_merge_amounts_for_kyc_check ( 348 TEH_pg, 349 &pmc->h_payto, 350 limit, 351 cb, 352 cb_cls); 353 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 354 "Got %d additional transactions for this merge and limit %llu\n", 355 qs, 356 (unsigned long long) limit.abs_value_us); 357 GNUNET_break (qs >= 0); 358 return qs; 359 } 360 361 362 /** 363 * Execute database transaction for /purses/$PID/merge. Runs the transaction 364 * logic; IF it returns a non-error code, the transaction logic MUST NOT queue 365 * a MHD response. IF it returns an hard error, the transaction logic MUST 366 * queue a MHD response and set @a mhd_ret. IF it returns the soft error 367 * code, the function MAY be called again to retry and MUST not queue a MHD 368 * response. 369 * 370 * @param cls a `struct PurseMergeContext` 371 * @param connection MHD request context 372 * @param[out] mhd_ret set to MHD status on error 373 * @return transaction status 374 */ 375 static enum GNUNET_DB_QueryStatus 376 merge_transaction (void *cls, 377 struct MHD_Connection *connection, 378 MHD_RESULT *mhd_ret) 379 { 380 struct PurseMergeContext *pmc = cls; 381 enum GNUNET_DB_QueryStatus qs; 382 bool in_conflict = true; 383 bool no_balance = true; 384 bool no_partner = true; 385 386 qs = TALER_EXCHANGEDB_do_purse_merge ( 387 TEH_pg, 388 &pmc->purse_pub, 389 &pmc->merge_sig, 390 pmc->merge_timestamp, 391 &pmc->reserve_sig, 392 pmc->provider_url, 393 &pmc->account_pub.reserve_pub, 394 &no_partner, 395 &no_balance, 396 &in_conflict); 397 if (qs < 0) 398 { 399 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 400 return qs; 401 GNUNET_break (0); 402 *mhd_ret = 403 TALER_MHD_reply_with_error (connection, 404 MHD_HTTP_INTERNAL_SERVER_ERROR, 405 TALER_EC_GENERIC_DB_STORE_FAILED, 406 "purse merge"); 407 return qs; 408 } 409 if (no_partner) 410 { 411 *mhd_ret = 412 TALER_MHD_reply_with_error (connection, 413 MHD_HTTP_NOT_FOUND, 414 TALER_EC_EXCHANGE_MERGE_PURSE_PARTNER_UNKNOWN, 415 pmc->provider_url); 416 return GNUNET_DB_STATUS_HARD_ERROR; 417 } 418 if (no_balance) 419 { 420 *mhd_ret = 421 TALER_MHD_reply_with_error (connection, 422 MHD_HTTP_PAYMENT_REQUIRED, 423 TALER_EC_EXCHANGE_PURSE_NOT_FULL, 424 NULL); 425 return GNUNET_DB_STATUS_HARD_ERROR; 426 } 427 if (in_conflict) 428 { 429 struct TALER_PurseMergeSignatureP merge_sig; 430 struct GNUNET_TIME_Timestamp merge_timestamp; 431 char *partner_url = NULL; 432 struct TALER_ReservePublicKeyP reserve_pub; 433 bool refunded; 434 435 qs = TALER_TALER_EXCHANGEDB_select_purse_merge (TEH_pg, 436 &pmc->purse_pub, 437 &merge_sig, 438 &merge_timestamp, 439 &partner_url, 440 &reserve_pub, 441 &refunded); 442 if (qs <= 0) 443 { 444 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 445 return qs; 446 TALER_LOG_WARNING ( 447 "Failed to fetch merge purse information from database\n"); 448 *mhd_ret = 449 TALER_MHD_reply_with_error (connection, 450 MHD_HTTP_INTERNAL_SERVER_ERROR, 451 TALER_EC_GENERIC_DB_FETCH_FAILED, 452 "select purse merge"); 453 return qs; 454 } 455 if (refunded) 456 { 457 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 458 "Purse was already refunded\n"); 459 *mhd_ret = TALER_MHD_reply_with_error (connection, 460 MHD_HTTP_GONE, 461 TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED, 462 NULL); 463 GNUNET_free (partner_url); 464 return GNUNET_DB_STATUS_HARD_ERROR; 465 } 466 if (0 != 467 GNUNET_memcmp (&merge_sig, 468 &pmc->merge_sig)) 469 { 470 *mhd_ret = TALER_MHD_REPLY_JSON_PACK ( 471 connection, 472 MHD_HTTP_CONFLICT, 473 GNUNET_JSON_pack_timestamp ("merge_timestamp", 474 merge_timestamp), 475 GNUNET_JSON_pack_data_auto ("merge_sig", 476 &merge_sig), 477 GNUNET_JSON_pack_allow_null ( 478 GNUNET_JSON_pack_string ("partner_url", 479 partner_url)), 480 GNUNET_JSON_pack_data_auto ("reserve_pub", 481 &reserve_pub)); 482 GNUNET_free (partner_url); 483 return GNUNET_DB_STATUS_HARD_ERROR; 484 } 485 /* idempotent! */ 486 *mhd_ret = reply_merge_success (pmc); 487 GNUNET_free (partner_url); 488 return GNUNET_DB_STATUS_HARD_ERROR; 489 } 490 491 return qs; 492 } 493 494 495 /** 496 * Purse-merge-specific cleanup routine. Function called 497 * upon completion of the request that should 498 * clean up @a rh_ctx. Can be NULL. 499 * 500 * @param rc request context to clean up 501 */ 502 static void 503 clean_purse_merge_rc (struct TEH_RequestContext *rc) 504 { 505 struct PurseMergeContext *pmc = rc->rh_ctx; 506 507 if (NULL != pmc->lch) 508 { 509 TEH_legitimization_check_cancel (pmc->lch); 510 pmc->lch = NULL; 511 } 512 GNUNET_free (pmc->provider_url); 513 GNUNET_free (pmc); 514 } 515 516 517 MHD_RESULT 518 TEH_handler_purses_merge ( 519 struct TEH_RequestContext *rc, 520 const struct TALER_PurseContractPublicKeyP *purse_pub, 521 const json_t *root) 522 { 523 struct PurseMergeContext *pmc = rc->rh_ctx; 524 525 if (NULL == pmc) 526 { 527 pmc = GNUNET_new (struct PurseMergeContext); 528 rc->rh_ctx = pmc; 529 rc->rh_cleaner = &clean_purse_merge_rc; 530 pmc->rc = rc; 531 pmc->purse_pub = *purse_pub; 532 pmc->exchange_timestamp 533 = GNUNET_TIME_timestamp_get (); 534 535 { 536 struct GNUNET_JSON_Specification spec[] = { 537 TALER_JSON_spec_normalized_payto_uri ("payto_uri", 538 &pmc->payto_uri), 539 GNUNET_JSON_spec_fixed_auto ("reserve_sig", 540 &pmc->reserve_sig), 541 GNUNET_JSON_spec_fixed_auto ("merge_sig", 542 &pmc->merge_sig), 543 GNUNET_JSON_spec_timestamp ("merge_timestamp", 544 &pmc->merge_timestamp), 545 GNUNET_JSON_spec_end () 546 }; 547 enum GNUNET_GenericReturnValue res; 548 549 res = TALER_MHD_parse_json_data (rc->connection, 550 root, 551 spec); 552 if (GNUNET_SYSERR == res) 553 { 554 GNUNET_break (0); 555 return MHD_NO; /* hard failure */ 556 } 557 if (GNUNET_NO == res) 558 { 559 GNUNET_break_op (0); 560 return MHD_YES; /* failure */ 561 } 562 } 563 564 { 565 struct TALER_PurseContractSignatureP purse_sig; 566 enum GNUNET_DB_QueryStatus qs; 567 568 /* Fetch purse details */ 569 qs = TALER_EXCHANGEDB_get_purse_request ( 570 TEH_pg, 571 &pmc->purse_pub, 572 &pmc->merge_pub, 573 &pmc->purse_expiration, 574 &pmc->h_contract_terms, 575 &pmc->min_age, 576 &pmc->target_amount, 577 &pmc->balance, 578 &purse_sig); 579 switch (qs) 580 { 581 case GNUNET_DB_STATUS_HARD_ERROR: 582 GNUNET_break (0); 583 return TALER_MHD_reply_with_error ( 584 rc->connection, 585 MHD_HTTP_INTERNAL_SERVER_ERROR, 586 TALER_EC_GENERIC_DB_FETCH_FAILED, 587 "select purse request"); 588 case GNUNET_DB_STATUS_SOFT_ERROR: 589 GNUNET_break (0); 590 return TALER_MHD_reply_with_error ( 591 rc->connection, 592 MHD_HTTP_INTERNAL_SERVER_ERROR, 593 TALER_EC_GENERIC_DB_FETCH_FAILED, 594 "select purse request"); 595 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 596 return TALER_MHD_reply_with_error ( 597 rc->connection, 598 MHD_HTTP_NOT_FOUND, 599 TALER_EC_EXCHANGE_GENERIC_PURSE_UNKNOWN, 600 NULL); 601 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 602 /* continued below */ 603 break; 604 } 605 } 606 607 /* check signatures */ 608 if (GNUNET_OK != 609 TALER_wallet_purse_merge_verify ( 610 pmc->payto_uri, 611 pmc->merge_timestamp, 612 &pmc->purse_pub, 613 &pmc->merge_pub, 614 &pmc->merge_sig)) 615 { 616 GNUNET_break_op (0); 617 return TALER_MHD_reply_with_error ( 618 rc->connection, 619 MHD_HTTP_FORBIDDEN, 620 TALER_EC_EXCHANGE_PURSE_MERGE_INVALID_MERGE_SIGNATURE, 621 NULL); 622 } 623 624 /* parse 'payto_uri' into pmc->account_pub and provider_url */ 625 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 626 "Received payto: `%s'\n", 627 pmc->payto_uri.normalized_payto); 628 if ( (0 != strncmp (pmc->payto_uri.normalized_payto, 629 "payto://taler-reserve/", 630 strlen ("payto://taler-reserve/"))) && 631 (0 != strncmp (pmc->payto_uri.normalized_payto, 632 "payto://taler-reserve-http/", 633 strlen ("payto://taler-reserve-http/"))) ) 634 { 635 GNUNET_break_op (0); 636 return TALER_MHD_reply_with_error ( 637 rc->connection, 638 MHD_HTTP_BAD_REQUEST, 639 TALER_EC_GENERIC_PARAMETER_MALFORMED, 640 "payto_uri"); 641 } 642 643 { 644 bool http; 645 const char *host; 646 const char *slash; 647 648 http = (0 == strncmp (pmc->payto_uri.normalized_payto, 649 "payto://taler-reserve-http/", 650 strlen ("payto://taler-reserve-http/"))); 651 host = &pmc->payto_uri.normalized_payto[http 652 ? strlen ("payto://taler-reserve-http/") 653 : strlen ("payto://taler-reserve/")]; 654 slash = strchr (host, 655 '/'); 656 if (NULL == slash) 657 { 658 GNUNET_break_op (0); 659 return TALER_MHD_reply_with_error ( 660 rc->connection, 661 MHD_HTTP_BAD_REQUEST, 662 TALER_EC_GENERIC_PARAMETER_MALFORMED, 663 "payto_uri"); 664 } 665 GNUNET_asprintf (&pmc->provider_url, 666 "%s://%.*s/", 667 http ? "http" : "https", 668 (int) (slash - host), 669 host); 670 slash++; 671 if (GNUNET_OK != 672 GNUNET_STRINGS_string_to_data ( 673 slash, 674 strlen (slash), 675 &pmc->account_pub.reserve_pub, 676 sizeof (pmc->account_pub.reserve_pub))) 677 { 678 GNUNET_break_op (0); 679 return TALER_MHD_reply_with_error ( 680 rc->connection, 681 MHD_HTTP_BAD_REQUEST, 682 TALER_EC_GENERIC_PARAMETER_MALFORMED, 683 "payto_uri"); 684 } 685 } 686 TALER_normalized_payto_hash (pmc->payto_uri, 687 &pmc->h_payto); 688 if (0 == strcmp (pmc->provider_url, 689 TEH_base_url)) 690 { 691 /* we use NULL to represent 'self' as the provider */ 692 GNUNET_free (pmc->provider_url); 693 } 694 else 695 { 696 char *method = GNUNET_strdup ("FIXME-WAD #7271"); 697 698 /* FIXME-#7271: lookup wire method by pmc.provider_url! */ 699 pmc->wf = TEH_wire_fees_by_time (pmc->exchange_timestamp, 700 method); 701 if (NULL == pmc->wf) 702 { 703 MHD_RESULT res; 704 705 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 706 "Cannot merge purse: wire fees not configured!\n"); 707 res = TALER_MHD_reply_with_error ( 708 rc->connection, 709 MHD_HTTP_INTERNAL_SERVER_ERROR, 710 TALER_EC_EXCHANGE_GENERIC_WIRE_FEES_MISSING, 711 method); 712 GNUNET_free (method); 713 return res; 714 } 715 GNUNET_free (method); 716 } 717 718 { 719 struct TALER_Amount zero_purse_fee; 720 721 GNUNET_assert (GNUNET_OK == 722 TALER_amount_set_zero ( 723 pmc->target_amount.currency, 724 &zero_purse_fee)); 725 if (GNUNET_OK != 726 TALER_wallet_account_merge_verify ( 727 pmc->merge_timestamp, 728 &pmc->purse_pub, 729 pmc->purse_expiration, 730 &pmc->h_contract_terms, 731 &pmc->target_amount, 732 &zero_purse_fee, 733 pmc->min_age, 734 TALER_WAMF_MODE_MERGE_FULLY_PAID_PURSE, 735 &pmc->account_pub.reserve_pub, 736 &pmc->reserve_sig)) 737 { 738 GNUNET_break_op (0); 739 return TALER_MHD_reply_with_error ( 740 rc->connection, 741 MHD_HTTP_FORBIDDEN, 742 TALER_EC_EXCHANGE_PURSE_MERGE_INVALID_RESERVE_SIGNATURE, 743 NULL); 744 } 745 } 746 { 747 struct TALER_FullPayto fake_full_payto; 748 749 GNUNET_asprintf (&fake_full_payto.full_payto, 750 "%s?receiver-name=wallet", 751 pmc->payto_uri.normalized_payto); 752 pmc->lch = TEH_legitimization_check ( 753 &rc->async_scope_id, 754 TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE, 755 fake_full_payto, 756 &pmc->h_payto, 757 &pmc->account_pub, 758 &amount_iterator, 759 pmc, 760 &legi_result_cb, 761 pmc); 762 GNUNET_free (fake_full_payto.full_payto); 763 } 764 GNUNET_assert (NULL != pmc->lch); 765 MHD_suspend_connection (rc->connection); 766 GNUNET_CONTAINER_DLL_insert (pmc_head, 767 pmc_tail, 768 pmc); 769 return MHD_YES; 770 } 771 if (NULL != pmc->response) 772 { 773 return MHD_queue_response (rc->connection, 774 pmc->http_status, 775 pmc->response); 776 } 777 if (! pmc->kyc.ok) 778 return TEH_RESPONSE_reply_kyc_required ( 779 rc->connection, 780 &pmc->h_payto, 781 &pmc->kyc, 782 false); 783 784 /* execute merge transaction */ 785 { 786 MHD_RESULT mhd_ret; 787 788 if (GNUNET_OK != 789 TEH_DB_run_transaction (rc->connection, 790 "execute purse merge", 791 TEH_MT_REQUEST_PURSE_MERGE, 792 &mhd_ret, 793 &merge_transaction, 794 pmc)) 795 { 796 return mhd_ret; 797 } 798 } 799 800 { 801 struct TALER_EXCHANGEDB_PurseEventP rep = { 802 .header.size = htons (sizeof (rep)), 803 .header.type = htons (TALER_DBEVENT_EXCHANGE_PURSE_MERGED), 804 .purse_pub = pmc->purse_pub 805 }; 806 807 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 808 "Notifying about purse merge\n"); 809 TALER_EXCHANGEDB_event_notify (TEH_pg, 810 &rep.header, 811 NULL, 812 0); 813 } 814 815 /* generate regular response */ 816 return reply_merge_success (pmc); 817 } 818 819 820 /* end of taler-exchange-httpd_purses_merge.c */