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