taler-exchange-httpd_post-purses-PURSE_PUB-merge.c (23782B)
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 if (NULL != pmc->response) 516 { 517 MHD_destroy_response (pmc->response); 518 pmc->response = NULL; 519 } 520 GNUNET_free (pmc->provider_url); 521 GNUNET_free (pmc); 522 } 523 524 525 enum MHD_Result 526 TEH_handler_purses_merge ( 527 struct TEH_RequestContext *rc, 528 const struct TALER_PurseContractPublicKeyP *purse_pub, 529 const json_t *root) 530 { 531 struct PurseMergeContext *pmc = rc->rh_ctx; 532 533 if (NULL == pmc) 534 { 535 pmc = GNUNET_new (struct PurseMergeContext); 536 rc->rh_ctx = pmc; 537 rc->rh_cleaner = &clean_purse_merge_rc; 538 pmc->rc = rc; 539 pmc->purse_pub = *purse_pub; 540 pmc->exchange_timestamp 541 = GNUNET_TIME_timestamp_get (); 542 543 { 544 struct GNUNET_JSON_Specification spec[] = { 545 TALER_JSON_spec_normalized_payto_uri ("payto_uri", 546 &pmc->payto_uri), 547 GNUNET_JSON_spec_fixed_auto ("reserve_sig", 548 &pmc->reserve_sig), 549 GNUNET_JSON_spec_fixed_auto ("merge_sig", 550 &pmc->merge_sig), 551 GNUNET_JSON_spec_timestamp ("merge_timestamp", 552 &pmc->merge_timestamp), 553 GNUNET_JSON_spec_end () 554 }; 555 enum GNUNET_GenericReturnValue res; 556 557 res = TALER_MHD_parse_json_data (rc->connection, 558 root, 559 spec); 560 if (GNUNET_SYSERR == res) 561 { 562 GNUNET_break (0); 563 return MHD_NO; /* hard failure */ 564 } 565 if (GNUNET_NO == res) 566 { 567 GNUNET_break_op (0); 568 return MHD_YES; /* failure */ 569 } 570 } 571 572 { 573 struct TALER_PurseContractSignatureP purse_sig; 574 enum GNUNET_DB_QueryStatus qs; 575 576 /* Fetch purse details */ 577 qs = TALER_EXCHANGEDB_get_purse_request ( 578 TEH_pg, 579 &pmc->purse_pub, 580 &pmc->merge_pub, 581 &pmc->purse_expiration, 582 &pmc->h_contract_terms, 583 &pmc->min_age, 584 &pmc->target_amount, 585 &pmc->balance, 586 &purse_sig); 587 switch (qs) 588 { 589 case GNUNET_DB_STATUS_HARD_ERROR: 590 GNUNET_break (0); 591 return TALER_MHD_reply_with_error ( 592 rc->connection, 593 MHD_HTTP_INTERNAL_SERVER_ERROR, 594 TALER_EC_GENERIC_DB_FETCH_FAILED, 595 "select purse request"); 596 case GNUNET_DB_STATUS_SOFT_ERROR: 597 GNUNET_break (0); 598 return TALER_MHD_reply_with_error ( 599 rc->connection, 600 MHD_HTTP_INTERNAL_SERVER_ERROR, 601 TALER_EC_GENERIC_DB_FETCH_FAILED, 602 "select purse request"); 603 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 604 return TALER_MHD_reply_with_error ( 605 rc->connection, 606 MHD_HTTP_NOT_FOUND, 607 TALER_EC_EXCHANGE_GENERIC_PURSE_UNKNOWN, 608 NULL); 609 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 610 /* continued below */ 611 break; 612 } 613 } 614 615 /* check signatures */ 616 if (GNUNET_OK != 617 TALER_wallet_purse_merge_verify ( 618 pmc->payto_uri, 619 pmc->merge_timestamp, 620 &pmc->purse_pub, 621 &pmc->merge_pub, 622 &pmc->merge_sig)) 623 { 624 GNUNET_break_op (0); 625 return TALER_MHD_reply_with_error ( 626 rc->connection, 627 MHD_HTTP_FORBIDDEN, 628 TALER_EC_EXCHANGE_PURSE_MERGE_INVALID_MERGE_SIGNATURE, 629 NULL); 630 } 631 632 /* parse 'payto_uri' into pmc->account_pub and provider_url */ 633 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 634 "Received payto: `%s'\n", 635 pmc->payto_uri.normalized_payto); 636 if ( (0 != strncmp (pmc->payto_uri.normalized_payto, 637 "payto://taler-reserve/", 638 strlen ("payto://taler-reserve/"))) && 639 (0 != strncmp (pmc->payto_uri.normalized_payto, 640 "payto://taler-reserve-http/", 641 strlen ("payto://taler-reserve-http/"))) ) 642 { 643 GNUNET_break_op (0); 644 return TALER_MHD_reply_with_error ( 645 rc->connection, 646 MHD_HTTP_BAD_REQUEST, 647 TALER_EC_GENERIC_PARAMETER_MALFORMED, 648 "payto_uri"); 649 } 650 651 { 652 bool http; 653 const char *host; 654 const char *slash; 655 656 http = (0 == strncmp (pmc->payto_uri.normalized_payto, 657 "payto://taler-reserve-http/", 658 strlen ("payto://taler-reserve-http/"))); 659 host = &pmc->payto_uri.normalized_payto[http 660 ? strlen ("payto://taler-reserve-http/") 661 : strlen ("payto://taler-reserve/")]; 662 slash = strchr (host, 663 '/'); 664 if (NULL == slash) 665 { 666 GNUNET_break_op (0); 667 return TALER_MHD_reply_with_error ( 668 rc->connection, 669 MHD_HTTP_BAD_REQUEST, 670 TALER_EC_GENERIC_PARAMETER_MALFORMED, 671 "payto_uri"); 672 } 673 GNUNET_asprintf (&pmc->provider_url, 674 "%s://%.*s/", 675 http ? "http" : "https", 676 (int) (slash - host), 677 host); 678 slash++; 679 if (GNUNET_OK != 680 GNUNET_STRINGS_string_to_data ( 681 slash, 682 strlen (slash), 683 &pmc->account_pub.reserve_pub, 684 sizeof (pmc->account_pub.reserve_pub))) 685 { 686 GNUNET_break_op (0); 687 return TALER_MHD_reply_with_error ( 688 rc->connection, 689 MHD_HTTP_BAD_REQUEST, 690 TALER_EC_GENERIC_PARAMETER_MALFORMED, 691 "payto_uri"); 692 } 693 } 694 TALER_normalized_payto_hash (pmc->payto_uri, 695 &pmc->h_payto); 696 if (0 == strcmp (pmc->provider_url, 697 TEH_base_url)) 698 { 699 /* we use NULL to represent 'self' as the provider */ 700 GNUNET_free (pmc->provider_url); 701 } 702 else 703 { 704 char *method = GNUNET_strdup ("FIXME-WAD #7271"); 705 706 /* FIXME-#7271: lookup wire method by pmc.provider_url! */ 707 pmc->wf = TEH_wire_fees_by_time (pmc->exchange_timestamp, 708 method); 709 if (NULL == pmc->wf) 710 { 711 enum MHD_Result res; 712 713 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 714 "Cannot merge purse: wire fees not configured!\n"); 715 res = TALER_MHD_reply_with_error ( 716 rc->connection, 717 MHD_HTTP_INTERNAL_SERVER_ERROR, 718 TALER_EC_EXCHANGE_GENERIC_WIRE_FEES_MISSING, 719 method); 720 GNUNET_free (method); 721 return res; 722 } 723 GNUNET_free (method); 724 } 725 726 { 727 struct TALER_Amount zero_purse_fee; 728 729 GNUNET_assert (GNUNET_OK == 730 TALER_amount_set_zero ( 731 pmc->target_amount.currency, 732 &zero_purse_fee)); 733 if (GNUNET_OK != 734 TALER_wallet_account_merge_verify ( 735 pmc->merge_timestamp, 736 &pmc->purse_pub, 737 pmc->purse_expiration, 738 &pmc->h_contract_terms, 739 &pmc->target_amount, 740 &zero_purse_fee, 741 pmc->min_age, 742 TALER_WAMF_MODE_MERGE_FULLY_PAID_PURSE, 743 &pmc->account_pub.reserve_pub, 744 &pmc->reserve_sig)) 745 { 746 GNUNET_break_op (0); 747 return TALER_MHD_reply_with_error ( 748 rc->connection, 749 MHD_HTTP_FORBIDDEN, 750 TALER_EC_EXCHANGE_PURSE_MERGE_INVALID_RESERVE_SIGNATURE, 751 NULL); 752 } 753 } 754 { 755 struct TALER_FullPayto fake_full_payto; 756 757 GNUNET_asprintf (&fake_full_payto.full_payto, 758 "%s?receiver-name=wallet", 759 pmc->payto_uri.normalized_payto); 760 pmc->lch = TEH_legitimization_check ( 761 &rc->async_scope_id, 762 TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE, 763 fake_full_payto, 764 &pmc->h_payto, 765 &pmc->account_pub, 766 &amount_iterator, 767 pmc, 768 &legi_result_cb, 769 pmc); 770 GNUNET_free (fake_full_payto.full_payto); 771 } 772 GNUNET_assert (NULL != pmc->lch); 773 MHD_suspend_connection (rc->connection); 774 GNUNET_CONTAINER_DLL_insert (pmc_head, 775 pmc_tail, 776 pmc); 777 return MHD_YES; 778 } 779 if (NULL != pmc->response) 780 { 781 return MHD_queue_response (rc->connection, 782 pmc->http_status, 783 pmc->response); 784 } 785 if (! pmc->kyc.ok) 786 return TEH_RESPONSE_reply_kyc_required ( 787 rc->connection, 788 &pmc->h_payto, 789 &pmc->kyc, 790 false); 791 792 /* execute merge transaction */ 793 { 794 enum MHD_Result mhd_ret; 795 796 if (GNUNET_OK != 797 TEH_DB_run_transaction (rc->connection, 798 "execute purse merge", 799 TEH_MT_REQUEST_PURSE_MERGE, 800 &mhd_ret, 801 &merge_transaction, 802 pmc)) 803 { 804 return mhd_ret; 805 } 806 } 807 808 { 809 struct TALER_EXCHANGEDB_PurseEventP rep = { 810 .header.size = htons (sizeof (rep)), 811 .header.type = htons (TALER_DBEVENT_EXCHANGE_PURSE_MERGED), 812 .purse_pub = pmc->purse_pub 813 }; 814 815 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 816 "Notifying about purse merge\n"); 817 TALER_EXCHANGEDB_event_notify (TEH_pg, 818 &rep.header, 819 NULL, 820 0); 821 } 822 823 /* generate regular response */ 824 return reply_merge_success (pmc); 825 } 826 827 828 /* end of taler-exchange-httpd_purses_merge.c */