taler-exchange-httpd_get-transfers-WTID.c (22198B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2014-2018, 2021 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_get-transfers-WTID.c 18 * @brief Handle wire transfer(s) GET requests 19 * @author Christian Grothoff 20 */ 21 #include <gnunet/gnunet_util_lib.h> 22 #include <jansson.h> 23 #include <microhttpd.h> 24 #include <pthread.h> 25 #include "taler-exchange-httpd_get-keys.h" 26 #include "taler-exchange-httpd_get-transfers-WTID.h" 27 #include "taler-exchange-httpd_responses.h" 28 #include "taler/taler_json_lib.h" 29 #include "taler/taler_mhd_lib.h" 30 #define TALER_EXCHANGEDB_REFUND_COIN_RESULT_CLOSURE struct TALER_Amount 31 #include "exchange-database/iterate_refunds_by_coin.h" 32 #include "exchange-database/get_wire_fee.h" 33 struct WtidTransactionContext; 34 #define TALER_EXCHANGEDB_AGGREGATION_DATA_RESULT_CLOSURE \ 35 struct WtidTransactionContext 36 #include "exchange-database/iterate_wire_transfers.h" 37 38 39 /** 40 * Information about one of the transactions that was 41 * aggregated, to be returned in the /transfers response. 42 */ 43 struct AggregatedDepositDetail 44 { 45 46 /** 47 * We keep deposit details in a DLL. 48 */ 49 struct AggregatedDepositDetail *next; 50 51 /** 52 * We keep deposit details in a DLL. 53 */ 54 struct AggregatedDepositDetail *prev; 55 56 /** 57 * Hash of the contract terms. 58 */ 59 struct TALER_PrivateContractHashP h_contract_terms; 60 61 /** 62 * Coin's public key of the deposited coin. 63 */ 64 struct TALER_CoinSpendPublicKeyP coin_pub; 65 66 /** 67 * Total value of the coin in the deposit (after 68 * refunds). 69 */ 70 struct TALER_Amount deposit_value; 71 72 /** 73 * Fees charged by the exchange for the deposit of this coin (possibly after reduction due to refunds). 74 */ 75 struct TALER_Amount deposit_fee; 76 77 /** 78 * Total amount refunded for this coin. 79 */ 80 struct TALER_Amount refund_total; 81 }; 82 83 84 /** 85 * A merchant asked for transaction details about a wire transfer. 86 * Provide them. Generates the 200 reply. 87 * 88 * @param connection connection to the client 89 * @param total total amount that was transferred 90 * @param merchant_pub public key of the merchant 91 * @param payto_uri destination account 92 * @param exchange_payto_uri our own account that was debited, `full_payto` 93 * may be NULL if we did not record it 94 * @param wire_fee wire fee that was charged 95 * @param exec_time execution time of the wire transfer 96 * @param wdd_head linked list with details about the combined deposits 97 * @return MHD result code 98 */ 99 static enum MHD_Result 100 reply_transfer_details (struct MHD_Connection *connection, 101 const struct TALER_Amount *total, 102 const struct TALER_MerchantPublicKeyP *merchant_pub, 103 const struct TALER_FullPayto payto_uri, 104 const struct TALER_FullPayto exchange_payto_uri, 105 const struct TALER_Amount *wire_fee, 106 struct GNUNET_TIME_Timestamp exec_time, 107 const struct AggregatedDepositDetail *wdd_head) 108 { 109 json_t *deposits; 110 struct GNUNET_HashContext *hash_context; 111 struct GNUNET_HashCode h_details; 112 struct TALER_ExchangePublicKeyP pub; 113 struct TALER_ExchangeSignatureP sig; 114 struct TALER_FullPaytoHashP h_payto; 115 116 deposits = json_array (); 117 GNUNET_assert (NULL != deposits); 118 hash_context = GNUNET_CRYPTO_hash_context_start (); 119 for (const struct AggregatedDepositDetail *wdd_pos = wdd_head; 120 NULL != wdd_pos; 121 wdd_pos = wdd_pos->next) 122 { 123 TALER_exchange_online_wire_deposit_append (hash_context, 124 &wdd_pos->h_contract_terms, 125 exec_time, 126 &wdd_pos->coin_pub, 127 &wdd_pos->deposit_value, 128 &wdd_pos->deposit_fee); 129 if (0 != 130 json_array_append_new ( 131 deposits, 132 GNUNET_JSON_PACK ( 133 GNUNET_JSON_pack_data_auto ("h_contract_terms", 134 &wdd_pos->h_contract_terms), 135 GNUNET_JSON_pack_data_auto ("coin_pub", 136 &wdd_pos->coin_pub), 137 138 GNUNET_JSON_pack_allow_null ( 139 TALER_JSON_pack_amount ("refund_total", 140 TALER_amount_is_zero ( 141 &wdd_pos->refund_total) 142 ? NULL 143 : &wdd_pos->refund_total)), 144 TALER_JSON_pack_amount ("deposit_value", 145 &wdd_pos->deposit_value), 146 TALER_JSON_pack_amount ("deposit_fee", 147 &wdd_pos->deposit_fee)))) 148 { 149 json_decref (deposits); 150 GNUNET_CRYPTO_hash_context_abort (hash_context); 151 return TALER_MHD_reply_with_error (connection, 152 MHD_HTTP_INTERNAL_SERVER_ERROR, 153 TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE, 154 "json_array_append_new() failed"); 155 } 156 } 157 GNUNET_CRYPTO_hash_context_finish (hash_context, 158 &h_details); 159 { 160 enum TALER_ErrorCode ec; 161 162 if (TALER_EC_NONE != 163 (ec = TALER_exchange_online_wire_deposit_sign ( 164 &TEH_keys_exchange_sign_, 165 total, 166 wire_fee, 167 merchant_pub, 168 payto_uri, 169 &h_details, 170 &pub, 171 &sig))) 172 { 173 json_decref (deposits); 174 return TALER_MHD_reply_with_ec (connection, 175 ec, 176 NULL); 177 } 178 } 179 180 TALER_full_payto_hash (payto_uri, 181 &h_payto); 182 return TALER_MHD_REPLY_JSON_PACK ( 183 connection, 184 MHD_HTTP_OK, 185 TALER_JSON_pack_amount ("total", 186 total), 187 TALER_JSON_pack_amount ("wire_fee", 188 wire_fee), 189 GNUNET_JSON_pack_data_auto ("merchant_pub", 190 merchant_pub), 191 GNUNET_JSON_pack_data_auto ("h_payto", 192 &h_payto), 193 /* Not covered by @e exchange_sig: this is a convenience for the 194 merchant's bookkeeping, not a claim the client needs to prove. */ 195 GNUNET_JSON_pack_allow_null ( 196 TALER_JSON_pack_full_payto ("exchange_payto_uri", 197 exchange_payto_uri)), 198 GNUNET_JSON_pack_timestamp ("execution_time", 199 exec_time), 200 GNUNET_JSON_pack_array_steal ("deposits", 201 deposits), 202 GNUNET_JSON_pack_data_auto ("exchange_sig", 203 &sig), 204 GNUNET_JSON_pack_data_auto ("exchange_pub", 205 &pub)); 206 } 207 208 209 /** 210 * Closure for #handle_transaction_data. 211 */ 212 struct WtidTransactionContext 213 { 214 215 /** 216 * Identifier of the wire transfer to track. 217 */ 218 struct TALER_WireTransferIdentifierRawP wtid; 219 220 /** 221 * Total amount of the wire transfer, as calculated by 222 * summing up the individual amounts. To be rounded down 223 * to calculate the real transfer amount at the end. 224 * Only valid if @e is_valid is #GNUNET_YES. 225 */ 226 struct TALER_Amount total; 227 228 /** 229 * Public key of the merchant, only valid if @e is_valid 230 * is #GNUNET_YES. 231 */ 232 struct TALER_MerchantPublicKeyP merchant_pub; 233 234 /** 235 * Wire fees applicable at @e exec_time. 236 */ 237 struct TALER_WireFeeSet fees; 238 239 /** 240 * Execution time of the wire transfer 241 */ 242 struct GNUNET_TIME_Timestamp exec_time; 243 244 /** 245 * Head of DLL with deposit details for transfers GET response. 246 */ 247 struct AggregatedDepositDetail *wdd_head; 248 249 /** 250 * Tail of DLL with deposit details for transfers GET response. 251 */ 252 struct AggregatedDepositDetail *wdd_tail; 253 254 /** 255 * Where were the funds wired? 256 */ 257 struct TALER_FullPayto payto_uri; 258 259 /** 260 * Which of our own accounts were the funds wired from? 261 * `full_payto` is NULL if we did not record this, which is 262 * the case for all transfers aggregated before the exchange 263 * started to store it. 264 */ 265 struct TALER_FullPayto exchange_payto_uri; 266 267 /** 268 * JSON array with details about the individual deposits. 269 */ 270 json_t *deposits; 271 272 /** 273 * Initially #GNUNET_NO, if we found no deposits so far. Set to 274 * #GNUNET_YES if we got transaction data, and the database replies 275 * remained consistent with respect to @e merchant_pub and @e h_wire 276 * (as they should). Set to #GNUNET_SYSERR if we encountered an 277 * internal error. 278 */ 279 enum GNUNET_GenericReturnValue is_valid; 280 281 /** 282 * Database status of a failed query inside the deposit 283 * iterator, used to distinguish a (retryable) serialization 284 * failure from a hard error when @e is_valid is #GNUNET_SYSERR. 285 */ 286 enum GNUNET_DB_QueryStatus qs; 287 288 }; 289 290 291 /** 292 * Callback that totals up the applicable refunds. 293 * 294 * @param total a `struct TALER_Amount` where we keep the total 295 * @param amount_with_fee amount being refunded 296 */ 297 static enum GNUNET_GenericReturnValue 298 add_refunds (struct TALER_Amount *total, 299 const struct TALER_Amount *amount_with_fee) 300 301 { 302 if (0 > 303 TALER_amount_add (total, 304 total, 305 amount_with_fee)) 306 { 307 GNUNET_break (0); 308 return GNUNET_SYSERR; 309 } 310 return GNUNET_OK; 311 } 312 313 314 /** 315 * Function called with the results of the lookup of the individual deposits 316 * that were aggregated for the given wire transfer. 317 * 318 * @param ctx our context for transmission 319 * @param rowid which row in the DB is the information from (for diagnostics), ignored 320 * @param merchant_pub public key of the merchant (should be same for all callbacks with the same @e cls) 321 * @param account_payto_uri where the funds were sent 322 * @param h_payto hash over @a account_payto_uri as it is in the DB 323 * @param exchange_payto_uri our own account the funds were sent from, 324 * `full_payto` NULL if we did not record it 325 * @param exec_time execution time of the wire transfer (should be same for all callbacks with the same @e cls) 326 * @param h_contract_terms which proposal was this payment about 327 * @param denom_pub denomination public key of the @a coin_pub (ignored) 328 * @param coin_pub which public key was this payment about 329 * @param deposit_value amount contributed by this coin in total (including fee) 330 * @param deposit_fee deposit fee charged by exchange for this coin 331 */ 332 static void 333 handle_deposit_data ( 334 struct WtidTransactionContext *ctx, 335 uint64_t rowid, 336 const struct TALER_MerchantPublicKeyP *merchant_pub, 337 const struct TALER_FullPayto account_payto_uri, 338 const struct TALER_FullPaytoHashP *h_payto, 339 const struct TALER_FullPayto exchange_payto_uri, 340 struct GNUNET_TIME_Timestamp exec_time, 341 const struct TALER_PrivateContractHashP *h_contract_terms, 342 const struct TALER_DenominationPublicKey *denom_pub, 343 const struct TALER_CoinSpendPublicKeyP *coin_pub, 344 const struct TALER_Amount *deposit_value, 345 const struct TALER_Amount *deposit_fee) 346 { 347 struct TALER_Amount total_refunds; 348 struct TALER_Amount dval; 349 struct TALER_Amount dfee; 350 enum GNUNET_DB_QueryStatus qs; 351 352 (void) rowid; 353 (void) denom_pub; 354 (void) h_payto; 355 if (GNUNET_SYSERR == ctx->is_valid) 356 return; 357 GNUNET_assert (GNUNET_OK == 358 TALER_amount_set_zero (deposit_value->currency, 359 &total_refunds)); 360 qs = TALER_EXCHANGEDB_iterate_refunds_by_coin (TEH_pg, 361 coin_pub, 362 merchant_pub, 363 h_contract_terms, 364 &add_refunds, 365 &total_refunds); 366 if (qs < 0) 367 { 368 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 369 ctx->qs = qs; 370 ctx->is_valid = GNUNET_SYSERR; 371 return; 372 } 373 if (1 == 374 TALER_amount_cmp (&total_refunds, 375 deposit_value)) 376 { 377 /* Refunds exceeded total deposit? not OK! */ 378 GNUNET_break (0); 379 ctx->is_valid = GNUNET_SYSERR; 380 return; 381 } 382 if (0 == 383 TALER_amount_cmp (&total_refunds, 384 deposit_value)) 385 { 386 /* total_refunds == deposit_value; 387 in this case, the total contributed to the 388 wire transfer is zero (as are fees) */ 389 GNUNET_assert (GNUNET_OK == 390 TALER_amount_set_zero (deposit_value->currency, 391 &dval)); 392 GNUNET_assert (GNUNET_OK == 393 TALER_amount_set_zero (deposit_value->currency, 394 &dfee)); 395 396 } 397 else 398 { 399 /* Compute deposit value by subtracting refunds */ 400 GNUNET_assert (0 < 401 TALER_amount_subtract (&dval, 402 deposit_value, 403 &total_refunds)); 404 if (-1 == 405 TALER_amount_cmp (&dval, 406 deposit_fee)) 407 { 408 /* dval < deposit_fee, so after refunds less than 409 the deposit fee remains; reduce deposit fee to 410 the remaining value of the coin */ 411 dfee = dval; 412 } 413 else 414 { 415 /* Partial refund, deposit fee remains */ 416 dfee = *deposit_fee; 417 } 418 } 419 420 if (GNUNET_NO == ctx->is_valid) 421 { 422 /* First one we encounter, setup general information in 'ctx' */ 423 ctx->merchant_pub = *merchant_pub; 424 ctx->payto_uri.full_payto = GNUNET_strdup (account_payto_uri.full_payto); 425 if (NULL != exchange_payto_uri.full_payto) 426 ctx->exchange_payto_uri.full_payto 427 = GNUNET_strdup (exchange_payto_uri.full_payto); 428 ctx->exec_time = exec_time; 429 ctx->is_valid = GNUNET_YES; 430 if (0 > 431 TALER_amount_subtract (&ctx->total, 432 &dval, 433 &dfee)) 434 { 435 GNUNET_break (0); 436 ctx->is_valid = GNUNET_SYSERR; 437 return; 438 } 439 } 440 else 441 { 442 struct TALER_Amount delta; 443 444 /* Subsequent data, check general information matches that in 'ctx'; 445 (it should, otherwise the deposits should not have been aggregated) */ 446 if ( (0 != GNUNET_memcmp (&ctx->merchant_pub, 447 merchant_pub)) || 448 (0 != TALER_full_payto_cmp (account_payto_uri, 449 ctx->payto_uri)) ) 450 { 451 GNUNET_break (0); 452 ctx->is_valid = GNUNET_SYSERR; 453 return; 454 } 455 if (0 > 456 TALER_amount_subtract (&delta, 457 &dval, 458 &dfee)) 459 { 460 GNUNET_break (0); 461 ctx->is_valid = GNUNET_SYSERR; 462 return; 463 } 464 if (0 > 465 TALER_amount_add (&ctx->total, 466 &ctx->total, 467 &delta)) 468 { 469 GNUNET_break (0); 470 ctx->is_valid = GNUNET_SYSERR; 471 return; 472 } 473 } 474 475 { 476 struct AggregatedDepositDetail *wdd; 477 478 wdd = GNUNET_new (struct AggregatedDepositDetail); 479 wdd->deposit_value = dval; 480 wdd->deposit_fee = dfee; 481 wdd->refund_total = total_refunds; 482 wdd->h_contract_terms = *h_contract_terms; 483 wdd->coin_pub = *coin_pub; 484 GNUNET_CONTAINER_DLL_insert (ctx->wdd_head, 485 ctx->wdd_tail, 486 wdd); 487 } 488 } 489 490 491 /** 492 * Free data structure reachable from @a ctx, but not @a ctx itself. 493 * 494 * @param ctx context to free 495 */ 496 static void 497 free_ctx (struct WtidTransactionContext *ctx) 498 { 499 struct AggregatedDepositDetail *wdd; 500 501 while (NULL != (wdd = ctx->wdd_head)) 502 { 503 GNUNET_CONTAINER_DLL_remove (ctx->wdd_head, 504 ctx->wdd_tail, 505 wdd); 506 GNUNET_free (wdd); 507 } 508 GNUNET_free (ctx->payto_uri.full_payto); 509 GNUNET_free (ctx->exchange_payto_uri.full_payto); 510 } 511 512 513 /** 514 * Execute a "/transfers" GET operation. Returns the deposit details of the 515 * deposits that were aggregated to create the given wire transfer. 516 * 517 * If it returns a non-error code, the transaction logic MUST 518 * NOT queue a MHD response. IF it returns an hard error, the 519 * transaction logic MUST queue a MHD response and set @a mhd_ret. IF 520 * it returns the soft error code, the function MAY be called again to 521 * retry and MUST not queue a MHD response. 522 * 523 * @param cls closure 524 * @param connection MHD request which triggered the transaction 525 * @param[out] mhd_ret set to MHD response status for @a connection, 526 * if transaction failed (!) 527 * @return transaction status 528 */ 529 static enum GNUNET_DB_QueryStatus 530 get_transfer_deposits (void *cls, 531 struct MHD_Connection *connection, 532 enum MHD_Result *mhd_ret) 533 { 534 struct WtidTransactionContext *ctx = cls; 535 enum GNUNET_DB_QueryStatus qs; 536 struct GNUNET_TIME_Timestamp wire_fee_start_date; 537 struct GNUNET_TIME_Timestamp wire_fee_end_date; 538 struct TALER_MasterSignatureP wire_fee_master_sig; 539 540 /* resetting to NULL/0 in case transaction was repeated after 541 serialization failure */ 542 free_ctx (ctx); 543 ctx->is_valid = GNUNET_NO; 544 ctx->qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; 545 qs = TALER_EXCHANGEDB_iterate_wire_transfers (TEH_pg, 546 &ctx->wtid, 547 &handle_deposit_data, 548 ctx); 549 if (0 > qs) 550 { 551 if (GNUNET_DB_STATUS_HARD_ERROR == qs) 552 { 553 GNUNET_break (0); 554 *mhd_ret = TALER_MHD_reply_with_error ( 555 connection, 556 MHD_HTTP_INTERNAL_SERVER_ERROR, 557 TALER_EC_GENERIC_DB_FETCH_FAILED, 558 "wire transfer"); 559 } 560 return qs; 561 } 562 if (GNUNET_SYSERR == ctx->is_valid) 563 { 564 if (GNUNET_DB_STATUS_SOFT_ERROR == ctx->qs) 565 return GNUNET_DB_STATUS_SOFT_ERROR; /* serialization issue, retry */ 566 GNUNET_break (0); 567 *mhd_ret = TALER_MHD_reply_with_error ( 568 connection, 569 MHD_HTTP_INTERNAL_SERVER_ERROR, 570 TALER_EC_GENERIC_DB_INVARIANT_FAILURE, 571 "wire history malformed"); 572 return GNUNET_DB_STATUS_HARD_ERROR; 573 } 574 if (GNUNET_NO == ctx->is_valid) 575 { 576 *mhd_ret = TALER_MHD_reply_with_error ( 577 connection, 578 MHD_HTTP_NOT_FOUND, 579 TALER_EC_EXCHANGE_TRANSFERS_GET_WTID_NOT_FOUND, 580 NULL); 581 return GNUNET_DB_STATUS_HARD_ERROR; 582 } 583 { 584 char *wire_method; 585 uint64_t rowid; 586 587 wire_method = TALER_payto_get_method (ctx->payto_uri.full_payto); 588 if (NULL == wire_method) 589 { 590 GNUNET_break (0); 591 *mhd_ret = TALER_MHD_reply_with_error ( 592 connection, 593 MHD_HTTP_INTERNAL_SERVER_ERROR, 594 TALER_EC_GENERIC_DB_INVARIANT_FAILURE, 595 "payto:// without wire method encountered"); 596 return GNUNET_DB_STATUS_HARD_ERROR; 597 } 598 qs = TALER_EXCHANGEDB_get_wire_fee (TEH_pg, 599 wire_method, 600 ctx->exec_time, 601 &rowid, 602 &wire_fee_start_date, 603 &wire_fee_end_date, 604 &ctx->fees, 605 &wire_fee_master_sig); 606 GNUNET_free (wire_method); 607 } 608 if (0 >= qs) 609 { 610 if ( (GNUNET_DB_STATUS_HARD_ERROR == qs) || 611 (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) ) 612 { 613 GNUNET_break (0); 614 *mhd_ret = TALER_MHD_reply_with_error ( 615 connection, 616 MHD_HTTP_INTERNAL_SERVER_ERROR, 617 TALER_EC_EXCHANGE_TRANSFERS_GET_WIRE_FEE_NOT_FOUND, 618 NULL); 619 } 620 return qs; 621 } 622 if (0 > 623 TALER_amount_subtract (&ctx->total, 624 &ctx->total, 625 &ctx->fees.wire)) 626 { 627 GNUNET_break (0); 628 *mhd_ret = TALER_MHD_reply_with_error ( 629 connection, 630 MHD_HTTP_INTERNAL_SERVER_ERROR, 631 TALER_EC_EXCHANGE_TRANSFERS_GET_WIRE_FEE_INCONSISTENT, 632 NULL); 633 return GNUNET_DB_STATUS_HARD_ERROR; 634 } 635 return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 636 } 637 638 639 enum MHD_Result 640 TEH_handler_transfers_get (struct TEH_RequestContext *rc, 641 const char *const args[1]) 642 { 643 struct WtidTransactionContext ctx; 644 enum MHD_Result mhd_ret; 645 646 memset (&ctx, 647 0, 648 sizeof (ctx)); 649 if (GNUNET_OK != 650 GNUNET_STRINGS_string_to_data (args[0], 651 strlen (args[0]), 652 &ctx.wtid, 653 sizeof (ctx.wtid))) 654 { 655 GNUNET_break_op (0); 656 return TALER_MHD_reply_with_error ( 657 rc->connection, 658 MHD_HTTP_BAD_REQUEST, 659 TALER_EC_EXCHANGE_TRANSFERS_GET_WTID_MALFORMED, 660 args[0]); 661 } 662 if (GNUNET_OK != 663 TEH_DB_run_transaction (rc->connection, 664 "run transfers GET", 665 TEH_MT_REQUEST_OTHER, 666 &mhd_ret, 667 &get_transfer_deposits, 668 &ctx)) 669 { 670 free_ctx (&ctx); 671 return mhd_ret; 672 } 673 mhd_ret = reply_transfer_details (rc->connection, 674 &ctx.total, 675 &ctx.merchant_pub, 676 ctx.payto_uri, 677 ctx.exchange_payto_uri, 678 &ctx.fees.wire, 679 ctx.exec_time, 680 ctx.wdd_head); 681 free_ctx (&ctx); 682 return mhd_ret; 683 } 684 685 686 /* end of taler-exchange-httpd_get-transfers-WTID.c */