taler-exchange-httpd_reserves_history.c (21559B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2014-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_history.c 18 * @brief Handle /reserves/$RESERVE_PUB HISTORY requests 19 * @author Florian Dold 20 * @author Benedikt Mueller 21 * @author Christian Grothoff 22 */ 23 #include "taler/platform.h" 24 #include <gnunet/gnunet_util_lib.h> 25 #include <jansson.h> 26 #include "taler/taler_json_lib.h" 27 #include "taler/taler_dbevents.h" 28 #include "taler-exchange-httpd_keys.h" 29 #include "taler-exchange-httpd_reserves_history.h" 30 #include "taler-exchange-httpd_responses.h" 31 32 33 /** 34 * Compile the history of a reserve into a JSON object. 35 * 36 * @param rh reserve history to JSON-ify 37 * @return json representation of the @a rh, NULL on error 38 */ 39 static json_t * 40 compile_reserve_history ( 41 const struct TALER_EXCHANGEDB_ReserveHistory *rh) 42 { 43 json_t *json_history; 44 45 json_history = json_array (); 46 GNUNET_assert (NULL != json_history); 47 for (const struct TALER_EXCHANGEDB_ReserveHistory *pos = rh; 48 NULL != pos; 49 pos = pos->next) 50 { 51 switch (pos->type) 52 { 53 case TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE: 54 { 55 const struct TALER_EXCHANGEDB_BankTransfer *bank = 56 pos->details.bank; 57 58 if (0 != 59 json_array_append_new ( 60 json_history, 61 GNUNET_JSON_PACK ( 62 GNUNET_JSON_pack_string ("type", 63 "CREDIT"), 64 GNUNET_JSON_pack_timestamp ("timestamp", 65 bank->execution_date), 66 TALER_JSON_pack_full_payto ("sender_account_url", 67 bank->sender_account_details), 68 GNUNET_JSON_pack_uint64 ("wire_reference", 69 bank->wire_reference), 70 TALER_JSON_pack_amount ("amount", 71 &bank->amount)))) 72 { 73 GNUNET_break (0); 74 json_decref (json_history); 75 return NULL; 76 } 77 break; 78 } 79 case TALER_EXCHANGEDB_RO_WITHDRAW_COINS: 80 { 81 const struct TALER_EXCHANGEDB_Withdraw *withdraw 82 = pos->details.withdraw; 83 struct TALER_Amount withdraw_fee; 84 struct TEH_KeyStateHandle *ksh; 85 86 GNUNET_assert (GNUNET_OK == 87 TALER_amount_set_zero ( 88 TEH_currency, 89 &withdraw_fee)); 90 91 /* 92 * We need to calculate the fees for the withdraw. 93 * Therefore, we need to get access to the key state. 94 */ 95 ksh = TEH_keys_get_state (); 96 if (NULL == ksh) 97 { 98 GNUNET_break (0); 99 json_decref (json_history); 100 return NULL; 101 } 102 103 for (size_t i = 0; i < withdraw->num_coins; i++) 104 { 105 /* Find the denomination and accumulate the fee */ 106 { 107 struct TEH_DenominationKey *dk; 108 dk = TEH_keys_denomination_by_hash_from_state ( 109 ksh, 110 &withdraw->denom_pub_hashes[i], 111 NULL, 112 NULL); 113 114 if (NULL == dk) 115 { 116 GNUNET_break (0); 117 json_decref (json_history); 118 return NULL; 119 } 120 121 if (0 > TALER_amount_add (&withdraw_fee, 122 &withdraw_fee, 123 &dk->meta.fees.withdraw)) 124 { 125 GNUNET_break (0); 126 json_decref (json_history); 127 return NULL; 128 } 129 } 130 } 131 132 /* Prepare the entry for the history */ 133 { 134 json_t *j_entry = GNUNET_JSON_PACK ( 135 GNUNET_JSON_pack_string ( 136 "type", 137 "WITHDRAW"), 138 GNUNET_JSON_pack_data_auto ( 139 "reserve_sig", 140 &withdraw->reserve_sig), 141 GNUNET_JSON_pack_data_auto ( 142 "planchets_h", 143 &withdraw->planchets_h), 144 GNUNET_JSON_pack_uint64 ( 145 "num_coins", 146 withdraw->num_coins), 147 TALER_JSON_pack_array_of_data ( 148 "denom_pub_hashes", 149 withdraw->num_coins, 150 withdraw->denom_pub_hashes, 151 sizeof(withdraw->denom_pub_hashes[0])), 152 TALER_JSON_pack_amount ( 153 "withdraw_fee", 154 &withdraw_fee), 155 TALER_JSON_pack_amount ( 156 "amount", 157 &withdraw->amount_with_fee) 158 ); 159 160 if (! withdraw->no_blinding_seed) 161 { 162 if (0!= 163 json_object_update_new ( 164 j_entry, 165 GNUNET_JSON_PACK ( 166 GNUNET_JSON_pack_data_auto ( 167 "blinding_seed", 168 &withdraw->blinding_seed)))) 169 { 170 GNUNET_break (0); 171 json_decref (json_history); 172 return NULL; 173 } 174 if (0!= 175 json_object_update_new ( 176 j_entry, 177 GNUNET_JSON_PACK ( 178 TALER_JSON_pack_array_of_data ( 179 "cs_r_values", 180 withdraw->num_cs_r_values, 181 withdraw->cs_r_values, 182 sizeof(withdraw->cs_r_values[0]))))) 183 { 184 GNUNET_break (0); 185 json_decref (json_history); 186 return NULL; 187 } 188 } 189 if (withdraw->age_proof_required) 190 { 191 if (0 != 192 json_object_update_new ( 193 j_entry, 194 GNUNET_JSON_PACK ( 195 GNUNET_JSON_pack_uint64 ( 196 "noreveal_index", 197 withdraw->noreveal_index), 198 GNUNET_JSON_pack_data_auto ( 199 "selected_h", 200 &withdraw->selected_h), 201 GNUNET_JSON_pack_uint64 ( 202 "max_age", 203 withdraw->max_age)))) 204 { 205 GNUNET_break (0); 206 json_decref (json_history); 207 return NULL; 208 } 209 } 210 211 if (0 != 212 json_array_append_new ( 213 json_history, 214 j_entry)) 215 { 216 GNUNET_break (0); 217 json_decref (json_history); 218 return NULL; 219 } 220 } 221 } 222 223 break; 224 case TALER_EXCHANGEDB_RO_RECOUP_COIN: 225 { 226 const struct TALER_EXCHANGEDB_Recoup *recoup 227 = pos->details.recoup; 228 struct TALER_ExchangePublicKeyP pub; 229 struct TALER_ExchangeSignatureP sig; 230 231 if (TALER_EC_NONE != 232 TALER_exchange_online_confirm_recoup_sign ( 233 &TEH_keys_exchange_sign_, 234 recoup->timestamp, 235 &recoup->value, 236 &recoup->coin.coin_pub, 237 &recoup->reserve_pub, 238 &pub, 239 &sig)) 240 { 241 GNUNET_break (0); 242 json_decref (json_history); 243 return NULL; 244 } 245 246 if (0 != 247 json_array_append_new ( 248 json_history, 249 GNUNET_JSON_PACK ( 250 GNUNET_JSON_pack_string ("type", 251 "RECOUP"), 252 GNUNET_JSON_pack_data_auto ("exchange_pub", 253 &pub), 254 GNUNET_JSON_pack_data_auto ("exchange_sig", 255 &sig), 256 GNUNET_JSON_pack_timestamp ("timestamp", 257 recoup->timestamp), 258 TALER_JSON_pack_amount ("amount", 259 &recoup->value), 260 GNUNET_JSON_pack_data_auto ("coin_pub", 261 &recoup->coin.coin_pub)))) 262 { 263 GNUNET_break (0); 264 json_decref (json_history); 265 return NULL; 266 } 267 } 268 break; 269 case TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK: 270 { 271 const struct TALER_EXCHANGEDB_ClosingTransfer *closing = 272 pos->details.closing; 273 struct TALER_ExchangePublicKeyP pub; 274 struct TALER_ExchangeSignatureP sig; 275 276 if (TALER_EC_NONE != 277 TALER_exchange_online_reserve_closed_sign ( 278 &TEH_keys_exchange_sign_, 279 closing->execution_date, 280 &closing->amount, 281 &closing->closing_fee, 282 closing->receiver_account_details, 283 &closing->wtid, 284 &pos->details.closing->reserve_pub, 285 &pub, 286 &sig)) 287 { 288 GNUNET_break (0); 289 json_decref (json_history); 290 return NULL; 291 } 292 if (0 != 293 json_array_append_new ( 294 json_history, 295 GNUNET_JSON_PACK ( 296 GNUNET_JSON_pack_string ("type", 297 "CLOSING"), 298 TALER_JSON_pack_full_payto ("receiver_account_details", 299 closing->receiver_account_details), 300 GNUNET_JSON_pack_data_auto ("wtid", 301 &closing->wtid), 302 GNUNET_JSON_pack_data_auto ("exchange_pub", 303 &pub), 304 GNUNET_JSON_pack_data_auto ("exchange_sig", 305 &sig), 306 GNUNET_JSON_pack_timestamp ("timestamp", 307 closing->execution_date), 308 TALER_JSON_pack_amount ("amount", 309 &closing->amount), 310 TALER_JSON_pack_amount ("closing_fee", 311 &closing->closing_fee)))) 312 { 313 GNUNET_break (0); 314 json_decref (json_history); 315 return NULL; 316 } 317 } 318 break; 319 case TALER_EXCHANGEDB_RO_PURSE_MERGE: 320 { 321 const struct TALER_EXCHANGEDB_PurseMerge *merge = 322 pos->details.merge; 323 324 if (0 != 325 json_array_append_new ( 326 json_history, 327 GNUNET_JSON_PACK ( 328 GNUNET_JSON_pack_string ("type", 329 "MERGE"), 330 GNUNET_JSON_pack_data_auto ("h_contract_terms", 331 &merge->h_contract_terms), 332 GNUNET_JSON_pack_data_auto ("merge_pub", 333 &merge->merge_pub), 334 GNUNET_JSON_pack_uint64 ("min_age", 335 merge->min_age), 336 GNUNET_JSON_pack_uint64 ("flags", 337 merge->flags), 338 GNUNET_JSON_pack_data_auto ("purse_pub", 339 &merge->purse_pub), 340 GNUNET_JSON_pack_data_auto ("reserve_sig", 341 &merge->reserve_sig), 342 GNUNET_JSON_pack_timestamp ("merge_timestamp", 343 merge->merge_timestamp), 344 GNUNET_JSON_pack_timestamp ("purse_expiration", 345 merge->purse_expiration), 346 TALER_JSON_pack_amount ("purse_fee", 347 &merge->purse_fee), 348 TALER_JSON_pack_amount ("amount", 349 &merge->amount_with_fee), 350 GNUNET_JSON_pack_bool ("merged", 351 merge->merged)))) 352 { 353 GNUNET_break (0); 354 json_decref (json_history); 355 return NULL; 356 } 357 } 358 break; 359 case TALER_EXCHANGEDB_RO_HISTORY_REQUEST: 360 { 361 const struct TALER_EXCHANGEDB_HistoryRequest *history = 362 pos->details.history; 363 364 if (0 != 365 json_array_append_new ( 366 json_history, 367 GNUNET_JSON_PACK ( 368 GNUNET_JSON_pack_string ("type", 369 "HISTORY"), 370 GNUNET_JSON_pack_data_auto ("reserve_sig", 371 &history->reserve_sig), 372 GNUNET_JSON_pack_timestamp ("request_timestamp", 373 history->request_timestamp), 374 TALER_JSON_pack_amount ("amount", 375 &history->history_fee)))) 376 { 377 GNUNET_break (0); 378 json_decref (json_history); 379 return NULL; 380 } 381 } 382 break; 383 384 case TALER_EXCHANGEDB_RO_OPEN_REQUEST: 385 { 386 const struct TALER_EXCHANGEDB_OpenRequest *orq = 387 pos->details.open_request; 388 389 if (0 != 390 json_array_append_new ( 391 json_history, 392 GNUNET_JSON_PACK ( 393 GNUNET_JSON_pack_string ("type", 394 "OPEN"), 395 GNUNET_JSON_pack_uint64 ("requested_min_purses", 396 orq->purse_limit), 397 GNUNET_JSON_pack_data_auto ("reserve_sig", 398 &orq->reserve_sig), 399 GNUNET_JSON_pack_timestamp ("request_timestamp", 400 orq->request_timestamp), 401 GNUNET_JSON_pack_timestamp ("requested_expiration", 402 orq->reserve_expiration), 403 TALER_JSON_pack_amount ("open_fee", 404 &orq->open_fee)))) 405 { 406 GNUNET_break (0); 407 json_decref (json_history); 408 return NULL; 409 } 410 } 411 break; 412 413 case TALER_EXCHANGEDB_RO_CLOSE_REQUEST: 414 { 415 const struct TALER_EXCHANGEDB_CloseRequest *crq = 416 pos->details.close_request; 417 418 if (0 != 419 json_array_append_new ( 420 json_history, 421 GNUNET_JSON_PACK ( 422 GNUNET_JSON_pack_string ("type", 423 "CLOSE"), 424 GNUNET_JSON_pack_data_auto ("reserve_sig", 425 &crq->reserve_sig), 426 GNUNET_is_zero (&crq->target_account_h_payto) 427 ? GNUNET_JSON_pack_allow_null ( 428 GNUNET_JSON_pack_string ("h_payto", 429 NULL)) 430 : GNUNET_JSON_pack_data_auto ("h_payto", 431 &crq->target_account_h_payto), 432 GNUNET_JSON_pack_timestamp ("request_timestamp", 433 crq->request_timestamp)))) 434 { 435 GNUNET_break (0); 436 json_decref (json_history); 437 return NULL; 438 } 439 } 440 break; 441 } 442 } 443 return json_history; 444 } 445 446 447 /** 448 * Add the headers we want to set for every /keys response. 449 * 450 * @param cls the key state to use 451 * @param[in,out] response the response to modify 452 */ 453 static void 454 add_response_headers (void *cls, 455 struct MHD_Response *response) 456 { 457 (void) cls; 458 GNUNET_break (MHD_YES == 459 MHD_add_response_header (response, 460 MHD_HTTP_HEADER_CACHE_CONTROL, 461 "no-cache")); 462 } 463 464 465 MHD_RESULT 466 TEH_handler_reserves_history ( 467 struct TEH_RequestContext *rc, 468 const struct TALER_ReservePublicKeyP *reserve_pub) 469 { 470 struct TALER_EXCHANGEDB_ReserveHistory *rh = NULL; 471 uint64_t start_off = 0; 472 struct TALER_Amount balance; 473 uint64_t etag_in; 474 uint64_t etag_out; 475 char etagp[24]; 476 struct MHD_Response *resp; 477 unsigned int http_status; 478 479 TALER_MHD_parse_request_number (rc->connection, 480 "start", 481 &start_off); 482 { 483 struct TALER_ReserveSignatureP reserve_sig; 484 bool required = true; 485 486 TALER_MHD_parse_request_header_auto (rc->connection, 487 TALER_RESERVE_HISTORY_SIGNATURE_HEADER, 488 &reserve_sig, 489 required); 490 491 if (GNUNET_OK != 492 TALER_wallet_reserve_history_verify (start_off, 493 reserve_pub, 494 &reserve_sig)) 495 { 496 GNUNET_break_op (0); 497 return TALER_MHD_reply_with_error (rc->connection, 498 MHD_HTTP_FORBIDDEN, 499 TALER_EC_EXCHANGE_RESERVE_HISTORY_BAD_SIGNATURE, 500 NULL); 501 } 502 } 503 504 /* Get etag */ 505 { 506 const char *etags; 507 508 etags = MHD_lookup_connection_value (rc->connection, 509 MHD_HEADER_KIND, 510 MHD_HTTP_HEADER_IF_NONE_MATCH); 511 if (NULL != etags) 512 { 513 char dummy; 514 unsigned long long ev; 515 516 if (1 != sscanf (etags, 517 "\"%llu\"%c", 518 &ev, 519 &dummy)) 520 { 521 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 522 "Client send malformed `If-None-Match' header `%s'\n", 523 etags); 524 etag_in = 0; 525 } 526 else 527 { 528 etag_in = (uint64_t) ev; 529 } 530 } 531 else 532 { 533 etag_in = start_off; 534 } 535 } 536 537 { 538 enum GNUNET_DB_QueryStatus qs; 539 540 qs = TEH_plugin->get_reserve_history (TEH_plugin->cls, 541 reserve_pub, 542 start_off, 543 etag_in, 544 &etag_out, 545 &balance, 546 &rh); 547 switch (qs) 548 { 549 case GNUNET_DB_STATUS_HARD_ERROR: 550 GNUNET_break (0); 551 return TALER_MHD_reply_with_error (rc->connection, 552 MHD_HTTP_INTERNAL_SERVER_ERROR, 553 TALER_EC_GENERIC_DB_FETCH_FAILED, 554 "get_reserve_history"); 555 case GNUNET_DB_STATUS_SOFT_ERROR: 556 return TALER_MHD_reply_with_error (rc->connection, 557 MHD_HTTP_INTERNAL_SERVER_ERROR, 558 TALER_EC_GENERIC_DB_SOFT_FAILURE, 559 "get_reserve_history"); 560 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 561 return TALER_MHD_reply_with_error (rc->connection, 562 MHD_HTTP_NOT_FOUND, 563 TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN, 564 NULL); 565 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 566 /* Handled below */ 567 break; 568 } 569 } 570 571 GNUNET_snprintf (etagp, 572 sizeof (etagp), 573 "\"%llu\"", 574 (unsigned long long) etag_out); 575 if (etag_in == etag_out) 576 { 577 TEH_plugin->free_reserve_history (TEH_plugin->cls, 578 rh); 579 return TEH_RESPONSE_reply_not_modified (rc->connection, 580 etagp, 581 &add_response_headers, 582 NULL); 583 } 584 if (NULL == rh) 585 { 586 /* 204: empty history */ 587 resp = MHD_create_response_from_buffer_static (0, 588 ""); 589 http_status = MHD_HTTP_NO_CONTENT; 590 } 591 else 592 { 593 json_t *history; 594 595 http_status = MHD_HTTP_OK; 596 history = compile_reserve_history (rh); 597 TEH_plugin->free_reserve_history (TEH_plugin->cls, 598 rh); 599 rh = NULL; 600 if (NULL == history) 601 { 602 GNUNET_break (0); 603 return TALER_MHD_reply_with_error (rc->connection, 604 MHD_HTTP_INTERNAL_SERVER_ERROR, 605 TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE, 606 NULL); 607 } 608 resp = TALER_MHD_MAKE_JSON_PACK ( 609 TALER_JSON_pack_amount ("balance", 610 &balance), 611 GNUNET_JSON_pack_array_steal ("history", 612 history)); 613 } 614 add_response_headers (NULL, 615 resp); 616 GNUNET_break (MHD_YES == 617 MHD_add_response_header (resp, 618 MHD_HTTP_HEADER_ETAG, 619 etagp)); 620 { 621 MHD_RESULT ret; 622 623 ret = MHD_queue_response (rc->connection, 624 http_status, 625 resp); 626 GNUNET_break (MHD_YES == ret); 627 MHD_destroy_response (resp); 628 return ret; 629 } 630 } 631 632 633 /* end of taler-exchange-httpd_reserves_history.c */