exchange_api_get-reserves-RESERVE_PUB-history.c (40291B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2014-2026 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU 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 General Public License for more details. 12 13 You should have received a copy of the GNU General Public License along with 14 TALER; see the file COPYING. If not, see 15 <http://www.gnu.org/licenses/> 16 */ 17 /** 18 * @file lib/exchange_api_get-reserves-RESERVE_PUB-history.c 19 * @brief Implementation of the GET /reserves/$RESERVE_PUB/history requests 20 * @author Christian Grothoff 21 */ 22 #include <jansson.h> 23 #include <microhttpd.h> /* just for HTTP history codes */ 24 #include <gnunet/gnunet_util_lib.h> 25 #include <gnunet/gnunet_json_lib.h> 26 #include <gnunet/gnunet_curl_lib.h> 27 #include "taler/taler_json_lib.h" 28 #include "exchange_api_handle.h" 29 #include "taler/taler_signatures.h" 30 #include "exchange_api_curl_defaults.h" 31 32 #define DEBUG 0 33 34 /** 35 * @brief A GET /reserves/$RESERVE_PUB/history Handle 36 */ 37 struct TALER_EXCHANGE_GetReservesHistoryHandle 38 { 39 40 /** 41 * Base URL of the exchange. 42 */ 43 char *base_url; 44 45 /** 46 * The url for this request. 47 */ 48 char *url; 49 50 /** 51 * The keys of the exchange this request handle will use. 52 */ 53 struct TALER_EXCHANGE_Keys *keys; 54 55 /** 56 * Handle for the request. 57 */ 58 struct GNUNET_CURL_Job *job; 59 60 /** 61 * Function to call with the result. 62 */ 63 TALER_EXCHANGE_GetReservesHistoryCallback cb; 64 65 /** 66 * Closure for @e cb. 67 */ 68 TALER_EXCHANGE_GET_RESERVES_HISTORY_RESULT_CLOSURE *cb_cls; 69 70 /** 71 * CURL context to use. 72 */ 73 struct GNUNET_CURL_Context *ctx; 74 75 /** 76 * Private key of the reserve we are querying. 77 */ 78 struct TALER_ReservePrivateKeyP reserve_priv; 79 80 /** 81 * Public key of the reserve we are querying. 82 */ 83 struct TALER_ReservePublicKeyP reserve_pub; 84 85 /** 86 * Where to store the etag (if any). 87 */ 88 uint64_t etag; 89 90 /** 91 * Options for the request. 92 */ 93 struct 94 { 95 /** 96 * Only return entries with offset strictly greater than this value. 97 */ 98 uint64_t start_off; 99 } options; 100 101 }; 102 103 104 /** 105 * Context for history entry helpers. 106 */ 107 struct HistoryParseContext 108 { 109 110 /** 111 * Keys of the exchange we use. 112 */ 113 const struct TALER_EXCHANGE_Keys *keys; 114 115 /** 116 * Our reserve public key. 117 */ 118 const struct TALER_ReservePublicKeyP *reserve_pub; 119 120 /** 121 * Array of UUIDs. 122 */ 123 struct GNUNET_HashCode *uuids; 124 125 /** 126 * Where to sum up total inbound amounts. 127 */ 128 struct TALER_Amount *total_in; 129 130 /** 131 * Where to sum up total outbound amounts. 132 */ 133 struct TALER_Amount *total_out; 134 135 /** 136 * Number of entries already used in @e uuids. 137 */ 138 unsigned int uuid_off; 139 }; 140 141 142 /** 143 * Type of a function called to parse a reserve history 144 * entry @a rh. 145 * 146 * @param[in,out] rh where to write the result 147 * @param[in,out] uc UUID context for duplicate detection 148 * @param transaction the transaction to parse 149 * @return #GNUNET_OK on success 150 */ 151 typedef enum GNUNET_GenericReturnValue 152 (*ParseHelper)(struct TALER_EXCHANGE_ReserveHistoryEntry *rh, 153 struct HistoryParseContext *uc, 154 const json_t *transaction); 155 156 157 /** 158 * Parse "credit" reserve history entry. 159 * 160 * @param[in,out] rh entry to parse 161 * @param uc our context 162 * @param transaction the transaction to parse 163 * @return #GNUNET_OK on success 164 */ 165 static enum GNUNET_GenericReturnValue 166 parse_credit (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, 167 struct HistoryParseContext *uc, 168 const json_t *transaction) 169 { 170 struct TALER_FullPayto wire_uri; 171 uint64_t wire_reference; 172 struct GNUNET_TIME_Timestamp timestamp; 173 struct GNUNET_JSON_Specification withdraw_spec[] = { 174 GNUNET_JSON_spec_uint64 ("wire_reference", 175 &wire_reference), 176 GNUNET_JSON_spec_timestamp ("timestamp", 177 ×tamp), 178 TALER_JSON_spec_full_payto_uri ("sender_account_url", 179 &wire_uri), 180 GNUNET_JSON_spec_end () 181 }; 182 183 rh->type = TALER_EXCHANGE_RTT_CREDIT; 184 if (0 > 185 TALER_amount_add (uc->total_in, 186 uc->total_in, 187 &rh->amount)) 188 { 189 /* overflow in history already!? inconceivable! Bad exchange! */ 190 GNUNET_break_op (0); 191 return GNUNET_SYSERR; 192 } 193 if (GNUNET_OK != 194 GNUNET_JSON_parse (transaction, 195 withdraw_spec, 196 NULL, NULL)) 197 { 198 GNUNET_break_op (0); 199 return GNUNET_SYSERR; 200 } 201 rh->details.in_details.sender_url.full_payto 202 = GNUNET_strdup (wire_uri.full_payto); 203 rh->details.in_details.wire_reference = wire_reference; 204 rh->details.in_details.timestamp = timestamp; 205 return GNUNET_OK; 206 } 207 208 209 /** 210 * Parse "withdraw" reserve history entry. 211 * 212 * @param[in,out] rh entry to parse 213 * @param uc our context 214 * @param transaction the transaction to parse 215 * @return #GNUNET_OK on success 216 */ 217 static enum GNUNET_GenericReturnValue 218 parse_withdraw (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, 219 struct HistoryParseContext *uc, 220 const json_t *transaction) 221 { 222 uint16_t num_coins; 223 struct TALER_Amount withdraw_fee; 224 struct TALER_Amount withdraw_amount; 225 struct TALER_Amount amount_without_fee; 226 uint8_t max_age = 0; 227 uint8_t noreveal_index = 0; 228 struct TALER_HashBlindedPlanchetsP planchets_h; 229 struct TALER_HashBlindedPlanchetsP selected_h; 230 struct TALER_ReserveSignatureP reserve_sig; 231 struct TALER_BlindingMasterSeedP blinding_seed; 232 struct TALER_DenominationHashP *denom_pub_hashes; 233 size_t num_denom_pub_hashes; 234 bool no_max_age; 235 bool no_noreveal_index; 236 bool no_blinding_seed; 237 bool no_selected_h; 238 struct GNUNET_JSON_Specification withdraw_spec[] = { 239 GNUNET_JSON_spec_fixed_auto ("reserve_sig", 240 &reserve_sig), 241 GNUNET_JSON_spec_uint16 ("num_coins", 242 &num_coins), 243 GNUNET_JSON_spec_fixed_auto ("planchets_h", 244 &planchets_h), 245 TALER_JSON_spec_amount_any ("amount", 246 &withdraw_amount), 247 TALER_JSON_spec_amount_any ("withdraw_fee", 248 &withdraw_fee), 249 TALER_JSON_spec_array_of_denom_pub_h ("denom_pub_hashes", 250 &num_denom_pub_hashes, 251 &denom_pub_hashes), 252 GNUNET_JSON_spec_mark_optional ( 253 GNUNET_JSON_spec_fixed_auto ("selected_h", 254 &selected_h), 255 &no_selected_h), 256 GNUNET_JSON_spec_mark_optional ( 257 GNUNET_JSON_spec_uint8 ("max_age", 258 &max_age), 259 &no_max_age), 260 GNUNET_JSON_spec_mark_optional ( 261 GNUNET_JSON_spec_fixed_auto ("blinding_seed", 262 &blinding_seed), 263 &no_blinding_seed), 264 GNUNET_JSON_spec_mark_optional ( 265 GNUNET_JSON_spec_uint8 ("noreveal_index", 266 &noreveal_index), 267 &no_noreveal_index), 268 GNUNET_JSON_spec_end () 269 }; 270 271 rh->type = TALER_EXCHANGE_RTT_WITHDRAWAL; 272 if (GNUNET_OK != 273 GNUNET_JSON_parse (transaction, 274 withdraw_spec, 275 NULL, NULL)) 276 { 277 GNUNET_break_op (0); 278 return GNUNET_SYSERR; 279 } 280 281 if ((no_max_age != no_noreveal_index) || 282 (no_max_age != no_selected_h)) 283 { 284 GNUNET_break_op (0); 285 GNUNET_JSON_parse_free (withdraw_spec); 286 return GNUNET_SYSERR; 287 } 288 rh->details.withdraw.age_restricted = ! no_max_age; 289 290 if (num_coins != num_denom_pub_hashes) 291 { 292 GNUNET_break_op (0); 293 GNUNET_JSON_parse_free (withdraw_spec); 294 return GNUNET_SYSERR; 295 } 296 297 /* Check that the signature is a valid withdraw request */ 298 if (0>TALER_amount_subtract ( 299 &amount_without_fee, 300 &withdraw_amount, 301 &withdraw_fee)) 302 { 303 GNUNET_break_op (0); 304 GNUNET_JSON_parse_free (withdraw_spec); 305 return GNUNET_SYSERR; 306 } 307 308 if (GNUNET_OK != 309 TALER_wallet_withdraw_verify ( 310 &amount_without_fee, 311 &withdraw_fee, 312 &planchets_h, 313 no_blinding_seed ? NULL : &blinding_seed, 314 no_max_age ? NULL : &uc->keys->age_mask, 315 no_max_age ? 0 : max_age, 316 uc->reserve_pub, 317 &reserve_sig)) 318 { 319 GNUNET_break_op (0); 320 GNUNET_JSON_parse_free (withdraw_spec); 321 return GNUNET_SYSERR; 322 } 323 324 rh->details.withdraw.num_coins = num_coins; 325 rh->details.withdraw.age_restricted = ! no_max_age; 326 rh->details.withdraw.max_age = max_age; 327 rh->details.withdraw.planchets_h = planchets_h; 328 if (! no_selected_h) 329 rh->details.withdraw.selected_h = selected_h; 330 rh->details.withdraw.noreveal_index = noreveal_index; 331 rh->details.withdraw.no_blinding_seed = no_blinding_seed; 332 rh->details.withdraw.reserve_sig = reserve_sig; 333 if (! no_blinding_seed) 334 rh->details.withdraw.blinding_seed = blinding_seed; 335 336 /* check that withdraw fee matches expectations! */ 337 { 338 const struct TALER_EXCHANGE_Keys *key_state; 339 struct TALER_Amount fee_acc; 340 struct TALER_Amount amount_acc; 341 342 GNUNET_assert (GNUNET_OK == 343 TALER_amount_set_zero (withdraw_amount.currency, 344 &fee_acc)); 345 GNUNET_assert (GNUNET_OK == 346 TALER_amount_set_zero (withdraw_amount.currency, 347 &amount_acc)); 348 349 key_state = uc->keys; 350 351 /* accumulate the withdraw fees */ 352 for (size_t i=0; i < num_coins; i++) 353 { 354 const struct TALER_EXCHANGE_DenomPublicKey *dki; 355 356 dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state, 357 &denom_pub_hashes[i]); 358 if (NULL == dki) 359 { 360 GNUNET_break_op (0); 361 GNUNET_JSON_parse_free (withdraw_spec); 362 return GNUNET_SYSERR; 363 } 364 if ( (0 > 365 TALER_amount_add (&fee_acc, 366 &fee_acc, 367 &dki->fees.withdraw)) || 368 (0 > 369 TALER_amount_add (&amount_acc, 370 &amount_acc, 371 &dki->value)) ) 372 { 373 GNUNET_break_op (0); 374 GNUNET_JSON_parse_free (withdraw_spec); 375 return GNUNET_SYSERR; 376 } 377 } 378 379 if ( (GNUNET_YES != 380 TALER_amount_cmp_currency (&fee_acc, 381 &withdraw_fee)) || 382 (0 != 383 TALER_amount_cmp (&fee_acc, 384 &withdraw_fee)) || 385 (0 != 386 TALER_amount_cmp (&amount_acc, 387 &amount_without_fee)) ) 388 { 389 GNUNET_break_op (0); 390 GNUNET_JSON_parse_free (withdraw_spec); 391 return GNUNET_SYSERR; 392 } 393 rh->details.withdraw.fee = withdraw_fee; 394 } 395 396 /* Check check that the same withdraw transaction 397 isn't listed twice by the exchange. We use the 398 "uuid" array to remember the hashes of all 399 signatures, and compare the hashes to find 400 duplicates. */ 401 GNUNET_CRYPTO_hash (&reserve_sig, 402 sizeof (reserve_sig), 403 &uc->uuids[uc->uuid_off]); 404 for (unsigned int i = 0; i<uc->uuid_off; i++) 405 { 406 if (0 == GNUNET_memcmp (&uc->uuids[uc->uuid_off], 407 &uc->uuids[i])) 408 { 409 GNUNET_break_op (0); 410 GNUNET_JSON_parse_free (withdraw_spec); 411 return GNUNET_SYSERR; 412 } 413 } 414 uc->uuid_off++; 415 416 if (0 > 417 TALER_amount_add (uc->total_out, 418 uc->total_out, 419 &rh->amount)) 420 { 421 /* overflow in history already!? inconceivable! Bad exchange! */ 422 GNUNET_break_op (0); 423 GNUNET_JSON_parse_free (withdraw_spec); 424 return GNUNET_SYSERR; 425 } 426 GNUNET_JSON_parse_free (withdraw_spec); 427 return GNUNET_OK; 428 } 429 430 431 /** 432 * Parse "recoup" reserve history entry. 433 * 434 * @param[in,out] rh entry to parse 435 * @param uc our context 436 * @param transaction the transaction to parse 437 * @return #GNUNET_OK on success 438 */ 439 static enum GNUNET_GenericReturnValue 440 parse_recoup (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, 441 struct HistoryParseContext *uc, 442 const json_t *transaction) 443 { 444 const struct TALER_EXCHANGE_Keys *key_state; 445 struct GNUNET_JSON_Specification recoup_spec[] = { 446 GNUNET_JSON_spec_fixed_auto ("coin_pub", 447 &rh->details.recoup_details.coin_pub), 448 GNUNET_JSON_spec_fixed_auto ("exchange_sig", 449 &rh->details.recoup_details.exchange_sig), 450 GNUNET_JSON_spec_fixed_auto ("exchange_pub", 451 &rh->details.recoup_details.exchange_pub), 452 GNUNET_JSON_spec_timestamp ("timestamp", 453 &rh->details.recoup_details.timestamp), 454 GNUNET_JSON_spec_fixed_auto ("planchets_h", 455 &rh->details.recoup_details.planchets_h), 456 GNUNET_JSON_spec_end () 457 }; 458 459 rh->type = TALER_EXCHANGE_RTT_RECOUP; 460 if (GNUNET_OK != 461 GNUNET_JSON_parse (transaction, 462 recoup_spec, 463 NULL, NULL)) 464 { 465 GNUNET_break_op (0); 466 return GNUNET_SYSERR; 467 } 468 key_state = uc->keys; 469 if (GNUNET_OK != 470 TALER_EXCHANGE_test_signing_key (key_state, 471 &rh->details. 472 recoup_details.exchange_pub)) 473 { 474 GNUNET_break_op (0); 475 return GNUNET_SYSERR; 476 } 477 if (GNUNET_OK != 478 TALER_exchange_online_confirm_recoup_verify ( 479 rh->details.recoup_details.timestamp, 480 &rh->amount, 481 &rh->details.recoup_details.coin_pub, 482 uc->reserve_pub, 483 &rh->details.recoup_details.exchange_pub, 484 &rh->details.recoup_details.exchange_sig)) 485 { 486 GNUNET_break_op (0); 487 return GNUNET_SYSERR; 488 } 489 if (0 > 490 TALER_amount_add (uc->total_in, 491 uc->total_in, 492 &rh->amount)) 493 { 494 /* overflow in history already!? inconceivable! Bad exchange! */ 495 GNUNET_break_op (0); 496 return GNUNET_SYSERR; 497 } 498 return GNUNET_OK; 499 } 500 501 502 /** 503 * Parse "closing" reserve history entry. 504 * 505 * @param[in,out] rh entry to parse 506 * @param uc our context 507 * @param transaction the transaction to parse 508 * @return #GNUNET_OK on success 509 */ 510 static enum GNUNET_GenericReturnValue 511 parse_closing (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, 512 struct HistoryParseContext *uc, 513 const json_t *transaction) 514 { 515 const struct TALER_EXCHANGE_Keys *key_state; 516 struct TALER_FullPayto receiver_uri; 517 struct GNUNET_JSON_Specification closing_spec[] = { 518 TALER_JSON_spec_full_payto_uri ( 519 "receiver_account_details", 520 &receiver_uri), 521 GNUNET_JSON_spec_fixed_auto ("wtid", 522 &rh->details.close_details.wtid), 523 GNUNET_JSON_spec_fixed_auto ("exchange_sig", 524 &rh->details.close_details.exchange_sig), 525 GNUNET_JSON_spec_fixed_auto ("exchange_pub", 526 &rh->details.close_details.exchange_pub), 527 TALER_JSON_spec_amount_any ("closing_fee", 528 &rh->details.close_details.fee), 529 GNUNET_JSON_spec_timestamp ("timestamp", 530 &rh->details.close_details.timestamp), 531 GNUNET_JSON_spec_end () 532 }; 533 534 rh->type = TALER_EXCHANGE_RTT_CLOSING; 535 if (GNUNET_OK != 536 GNUNET_JSON_parse (transaction, 537 closing_spec, 538 NULL, NULL)) 539 { 540 GNUNET_break_op (0); 541 return GNUNET_SYSERR; 542 } 543 key_state = uc->keys; 544 if (GNUNET_OK != 545 TALER_EXCHANGE_test_signing_key ( 546 key_state, 547 &rh->details.close_details.exchange_pub)) 548 { 549 GNUNET_break_op (0); 550 return GNUNET_SYSERR; 551 } 552 if (GNUNET_OK != 553 TALER_exchange_online_reserve_closed_verify ( 554 rh->details.close_details.timestamp, 555 &rh->amount, 556 &rh->details.close_details.fee, 557 receiver_uri, 558 &rh->details.close_details.wtid, 559 uc->reserve_pub, 560 &rh->details.close_details.exchange_pub, 561 &rh->details.close_details.exchange_sig)) 562 { 563 GNUNET_break_op (0); 564 return GNUNET_SYSERR; 565 } 566 if (0 > 567 TALER_amount_add (uc->total_out, 568 uc->total_out, 569 &rh->amount)) 570 { 571 /* overflow in history already!? inconceivable! Bad exchange! */ 572 GNUNET_break_op (0); 573 return GNUNET_SYSERR; 574 } 575 rh->details.close_details.receiver_account_details.full_payto 576 = GNUNET_strdup (receiver_uri.full_payto); 577 return GNUNET_OK; 578 } 579 580 581 /** 582 * Parse "merge" reserve history entry. 583 * 584 * @param[in,out] rh entry to parse 585 * @param uc our context 586 * @param transaction the transaction to parse 587 * @return #GNUNET_OK on success 588 */ 589 static enum GNUNET_GenericReturnValue 590 parse_merge (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, 591 struct HistoryParseContext *uc, 592 const json_t *transaction) 593 { 594 uint32_t flags32; 595 struct GNUNET_JSON_Specification merge_spec[] = { 596 GNUNET_JSON_spec_fixed_auto ("h_contract_terms", 597 &rh->details.merge_details.h_contract_terms), 598 GNUNET_JSON_spec_fixed_auto ("merge_pub", 599 &rh->details.merge_details.merge_pub), 600 GNUNET_JSON_spec_fixed_auto ("purse_pub", 601 &rh->details.merge_details.purse_pub), 602 GNUNET_JSON_spec_uint32 ("min_age", 603 &rh->details.merge_details.min_age), 604 GNUNET_JSON_spec_uint32 ("flags", 605 &flags32), 606 GNUNET_JSON_spec_fixed_auto ("reserve_sig", 607 &rh->details.merge_details.reserve_sig), 608 TALER_JSON_spec_amount_any ("purse_fee", 609 &rh->details.merge_details.purse_fee), 610 GNUNET_JSON_spec_timestamp ("merge_timestamp", 611 &rh->details.merge_details.merge_timestamp), 612 GNUNET_JSON_spec_timestamp ("purse_expiration", 613 &rh->details.merge_details.purse_expiration), 614 GNUNET_JSON_spec_bool ("merged", 615 &rh->details.merge_details.merged), 616 GNUNET_JSON_spec_end () 617 }; 618 619 rh->type = TALER_EXCHANGE_RTT_MERGE; 620 if (GNUNET_OK != 621 GNUNET_JSON_parse (transaction, 622 merge_spec, 623 NULL, NULL)) 624 { 625 GNUNET_break_op (0); 626 return GNUNET_SYSERR; 627 } 628 rh->details.merge_details.flags = 629 (enum TALER_WalletAccountMergeFlags) flags32; 630 if (GNUNET_OK != 631 TALER_wallet_account_merge_verify ( 632 rh->details.merge_details.merge_timestamp, 633 &rh->details.merge_details.purse_pub, 634 rh->details.merge_details.purse_expiration, 635 &rh->details.merge_details.h_contract_terms, 636 &rh->amount, 637 &rh->details.merge_details.purse_fee, 638 rh->details.merge_details.min_age, 639 rh->details.merge_details.flags, 640 uc->reserve_pub, 641 &rh->details.merge_details.reserve_sig)) 642 { 643 GNUNET_break_op (0); 644 return GNUNET_SYSERR; 645 } 646 if (rh->details.merge_details.merged) 647 { 648 if (0 > 649 TALER_amount_add (uc->total_in, 650 uc->total_in, 651 &rh->amount)) 652 { 653 /* overflow in history already!? inconceivable! Bad exchange! */ 654 GNUNET_break_op (0); 655 return GNUNET_SYSERR; 656 } 657 } 658 else 659 { 660 if (0 > 661 TALER_amount_add (uc->total_out, 662 uc->total_out, 663 &rh->details.merge_details.purse_fee)) 664 { 665 /* overflow in history already!? inconceivable! Bad exchange! */ 666 GNUNET_break_op (0); 667 return GNUNET_SYSERR; 668 } 669 } 670 return GNUNET_OK; 671 } 672 673 674 /** 675 * Parse "open" reserve open entry. 676 * 677 * @param[in,out] rh entry to parse 678 * @param uc our context 679 * @param transaction the transaction to parse 680 * @return #GNUNET_OK on success 681 */ 682 static enum GNUNET_GenericReturnValue 683 parse_open (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, 684 struct HistoryParseContext *uc, 685 const json_t *transaction) 686 { 687 struct GNUNET_JSON_Specification open_spec[] = { 688 GNUNET_JSON_spec_fixed_auto ("reserve_sig", 689 &rh->details.open_request.reserve_sig), 690 TALER_JSON_spec_amount_any ("open_fee", 691 &rh->details.open_request.reserve_payment), 692 GNUNET_JSON_spec_uint32 ("requested_min_purses", 693 &rh->details.open_request.purse_limit), 694 GNUNET_JSON_spec_timestamp ("request_timestamp", 695 &rh->details.open_request.request_timestamp), 696 GNUNET_JSON_spec_timestamp ("requested_expiration", 697 &rh->details.open_request.reserve_expiration), 698 GNUNET_JSON_spec_end () 699 }; 700 701 rh->type = TALER_EXCHANGE_RTT_OPEN; 702 if (GNUNET_OK != 703 GNUNET_JSON_parse (transaction, 704 open_spec, 705 NULL, NULL)) 706 { 707 GNUNET_break_op (0); 708 return GNUNET_SYSERR; 709 } 710 /* the entry has no "amount" of its own; the amount debited from 711 the reserve is the reserve payment for the open request */ 712 rh->amount = rh->details.open_request.reserve_payment; 713 if (GNUNET_OK != 714 TALER_wallet_reserve_open_verify ( 715 &rh->details.open_request.reserve_payment, 716 rh->details.open_request.request_timestamp, 717 rh->details.open_request.reserve_expiration, 718 rh->details.open_request.purse_limit, 719 uc->reserve_pub, 720 &rh->details.open_request.reserve_sig)) 721 { 722 GNUNET_break_op (0); 723 return GNUNET_SYSERR; 724 } 725 if (0 > 726 TALER_amount_add (uc->total_out, 727 uc->total_out, 728 &rh->details.open_request.reserve_payment)) 729 { 730 /* overflow in history already!? inconceivable! Bad exchange! */ 731 GNUNET_break_op (0); 732 return GNUNET_SYSERR; 733 } 734 return GNUNET_OK; 735 } 736 737 738 /** 739 * Parse "close" reserve close entry. 740 * 741 * @param[in,out] rh entry to parse 742 * @param uc our context 743 * @param transaction the transaction to parse 744 * @return #GNUNET_OK on success 745 */ 746 static enum GNUNET_GenericReturnValue 747 parse_close (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, 748 struct HistoryParseContext *uc, 749 const json_t *transaction) 750 { 751 struct GNUNET_JSON_Specification close_spec[] = { 752 GNUNET_JSON_spec_fixed_auto ("reserve_sig", 753 &rh->details.close_request.reserve_sig), 754 GNUNET_JSON_spec_mark_optional ( 755 GNUNET_JSON_spec_fixed_auto ("h_payto", 756 &rh->details.close_request. 757 target_account_h_payto), 758 NULL), 759 GNUNET_JSON_spec_timestamp ("request_timestamp", 760 &rh->details.close_request.request_timestamp), 761 GNUNET_JSON_spec_end () 762 }; 763 764 rh->type = TALER_EXCHANGE_RTT_CLOSE; 765 if (GNUNET_OK != 766 GNUNET_JSON_parse (transaction, 767 close_spec, 768 NULL, NULL)) 769 { 770 GNUNET_break_op (0); 771 return GNUNET_SYSERR; 772 } 773 /* force amount to invalid */ 774 memset (&rh->amount, 775 0, 776 sizeof (rh->amount)); 777 if (GNUNET_OK != 778 TALER_wallet_reserve_close_verify ( 779 rh->details.close_request.request_timestamp, 780 &rh->details.close_request.target_account_h_payto, 781 uc->reserve_pub, 782 &rh->details.close_request.reserve_sig)) 783 { 784 GNUNET_break_op (0); 785 return GNUNET_SYSERR; 786 } 787 return GNUNET_OK; 788 } 789 790 791 static void 792 free_reserve_history ( 793 unsigned int len, 794 struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static len]) 795 { 796 for (unsigned int i = 0; i<len; i++) 797 { 798 struct TALER_EXCHANGE_ReserveHistoryEntry *rhi = &rhistory[i]; 799 800 switch (rhi->type) 801 { 802 case TALER_EXCHANGE_RTT_CREDIT: 803 GNUNET_free (rhi->details.in_details.sender_url.full_payto); 804 break; 805 case TALER_EXCHANGE_RTT_WITHDRAWAL: 806 break; 807 case TALER_EXCHANGE_RTT_RECOUP: 808 break; 809 case TALER_EXCHANGE_RTT_CLOSING: 810 GNUNET_free ( 811 rhi->details.close_details.receiver_account_details.full_payto); 812 break; 813 case TALER_EXCHANGE_RTT_MERGE: 814 break; 815 case TALER_EXCHANGE_RTT_OPEN: 816 break; 817 case TALER_EXCHANGE_RTT_CLOSE: 818 break; 819 } 820 } 821 GNUNET_free (rhistory); 822 } 823 824 825 /** 826 * Parse history given in JSON format and return it in binary format. 827 * 828 * @param keys exchange keys 829 * @param history JSON array with the history 830 * @param reserve_pub public key of the reserve to inspect 831 * @param currency currency we expect the balance to be in 832 * @param[out] total_in set to value of credits to reserve 833 * @param[out] total_out set to value of debits from reserve 834 * @param history_length number of entries in @a history 835 * @param[out] rhistory array of length @a history_length, set to the 836 * parsed history entries 837 * @return #GNUNET_OK if history was valid and @a rhistory and @a balance 838 * were set, 839 * #GNUNET_SYSERR if there was a protocol violation in @a history 840 */ 841 static enum GNUNET_GenericReturnValue 842 parse_reserve_history ( 843 const struct TALER_EXCHANGE_Keys *keys, 844 const json_t *history, 845 const struct TALER_ReservePublicKeyP *reserve_pub, 846 const char *currency, 847 struct TALER_Amount *total_in, 848 struct TALER_Amount *total_out, 849 unsigned int history_length, 850 struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static history_length]) 851 { 852 const struct 853 { 854 const char *type; 855 ParseHelper helper; 856 } map[] = { 857 { "CREDIT", &parse_credit }, 858 { "WITHDRAW", &parse_withdraw }, 859 { "RECOUP", &parse_recoup }, 860 { "MERGE", &parse_merge }, 861 { "CLOSING", &parse_closing }, 862 { "OPEN", &parse_open }, 863 { "CLOSE", &parse_close }, 864 { NULL, NULL } 865 }; 866 struct GNUNET_HashCode *uuid 867 = GNUNET_new_array (history_length, 868 struct GNUNET_HashCode); 869 struct HistoryParseContext uc = { 870 .keys = keys, 871 .reserve_pub = reserve_pub, 872 .uuids = uuid, 873 .total_in = total_in, 874 .total_out = total_out 875 }; 876 877 GNUNET_assert (GNUNET_OK == 878 TALER_amount_set_zero (currency, 879 total_in)); 880 GNUNET_assert (GNUNET_OK == 881 TALER_amount_set_zero (currency, 882 total_out)); 883 for (unsigned int off = 0; off<history_length; off++) 884 { 885 struct TALER_EXCHANGE_ReserveHistoryEntry *rh = &rhistory[off]; 886 json_t *transaction; 887 const char *type; 888 bool no_amount = false; 889 struct GNUNET_JSON_Specification hist_spec[] = { 890 GNUNET_JSON_spec_string ("type", 891 &type), 892 /* 'OPEN' and 'CLOSE' entries have no 'amount'! */ 893 GNUNET_JSON_spec_mark_optional ( 894 TALER_JSON_spec_amount_any ("amount", 895 &rh->amount), 896 &no_amount), 897 GNUNET_JSON_spec_uint64 ("history_offset", 898 &rh->history_offset), 899 /* 'wire' and 'signature' are optional depending on 'type'! */ 900 GNUNET_JSON_spec_end () 901 }; 902 bool found = false; 903 904 transaction = json_array_get (history, 905 off); 906 if (GNUNET_OK != 907 GNUNET_JSON_parse (transaction, 908 hist_spec, 909 NULL, NULL)) 910 { 911 GNUNET_break_op (0); 912 #if DEBUG 913 json_dumpf (transaction, 914 stderr, 915 JSON_INDENT (2)); 916 #endif 917 GNUNET_free (uuid); 918 return GNUNET_SYSERR; 919 } 920 if (no_amount) 921 { 922 /* only 'OPEN' and 'CLOSE' entries may omit the amount */ 923 if ( (0 != strcasecmp ("OPEN", 924 type)) && 925 (0 != strcasecmp ("CLOSE", 926 type)) ) 927 { 928 GNUNET_break_op (0); 929 GNUNET_free (uuid); 930 return GNUNET_SYSERR; 931 } 932 memset (&rh->amount, 933 0, 934 sizeof (rh->amount)); 935 } 936 else if (GNUNET_YES != 937 TALER_amount_cmp_currency (&rh->amount, 938 total_in)) 939 { 940 GNUNET_break_op (0); 941 GNUNET_free (uuid); 942 return GNUNET_SYSERR; 943 } 944 for (unsigned int i = 0; NULL != map[i].type; i++) 945 { 946 if (0 == strcasecmp (map[i].type, 947 type)) 948 { 949 found = true; 950 if (GNUNET_OK != 951 map[i].helper (rh, 952 &uc, 953 transaction)) 954 { 955 GNUNET_break_op (0); 956 GNUNET_free (uuid); 957 return GNUNET_SYSERR; 958 } 959 break; 960 } 961 } 962 if (! found) 963 { 964 /* unexpected 'type', protocol incompatibility, complain! */ 965 GNUNET_break_op (0); 966 GNUNET_free (uuid); 967 return GNUNET_SYSERR; 968 } 969 } 970 GNUNET_free (uuid); 971 return GNUNET_OK; 972 } 973 974 975 /** 976 * Handle HTTP header received by curl. 977 * 978 * @param buffer one line of HTTP header data 979 * @param size size of an item 980 * @param nitems number of items passed 981 * @param userdata our `struct TALER_EXCHANGE_GetReservesHistoryHandle *` 982 * @return `size * nitems` 983 */ 984 static size_t 985 handle_header (char *buffer, 986 size_t size, 987 size_t nitems, 988 void *userdata) 989 { 990 struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh = userdata; 991 size_t total = size * nitems; 992 char *ndup; 993 const char *hdr_type; 994 char *hdr_val; 995 char *sp; 996 997 ndup = GNUNET_strndup (buffer, 998 total); 999 hdr_type = strtok_r (ndup, 1000 ":", 1001 &sp); 1002 if (NULL == hdr_type) 1003 { 1004 GNUNET_free (ndup); 1005 return total; 1006 } 1007 hdr_val = strtok_r (NULL, 1008 "\n\r", 1009 &sp); 1010 if (NULL == hdr_val) 1011 { 1012 GNUNET_free (ndup); 1013 return total; 1014 } 1015 if (' ' == *hdr_val) 1016 hdr_val++; 1017 if (0 == strcasecmp (hdr_type, 1018 MHD_HTTP_HEADER_ETAG)) 1019 { 1020 unsigned long long tval; 1021 char dummy; 1022 1023 if (1 != 1024 sscanf (hdr_val, 1025 "\"%llu\"%c", 1026 &tval, 1027 &dummy)) 1028 { 1029 /* Not an ETag we understand (weak validators are permitted by 1030 RFC 9110, Section 8.8.3); ignore it instead of aborting the 1031 transfer with a write error. */ 1032 GNUNET_break_op (0); 1033 GNUNET_free (ndup); 1034 return total; 1035 } 1036 grhh->etag = (uint64_t) tval; 1037 } 1038 GNUNET_free (ndup); 1039 return total; 1040 } 1041 1042 1043 /** 1044 * We received an #MHD_HTTP_OK status code. Handle the JSON response. 1045 * 1046 * @param grhh handle of the request 1047 * @param j JSON response 1048 * @return #GNUNET_OK on success 1049 */ 1050 static enum GNUNET_GenericReturnValue 1051 handle_reserves_history_ok ( 1052 struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh, 1053 const json_t *j) 1054 { 1055 const json_t *history; 1056 unsigned int len; 1057 struct TALER_EXCHANGE_GetReservesHistoryResponse rs = { 1058 .hr.reply = j, 1059 .hr.http_status = MHD_HTTP_OK, 1060 .details.ok.etag = grhh->etag 1061 }; 1062 struct GNUNET_JSON_Specification spec[] = { 1063 TALER_JSON_spec_amount_any ("balance", 1064 &rs.details.ok.balance), 1065 GNUNET_JSON_spec_array_const ("history", 1066 &history), 1067 GNUNET_JSON_spec_end () 1068 }; 1069 1070 if (GNUNET_OK != 1071 GNUNET_JSON_parse (j, 1072 spec, 1073 NULL, 1074 NULL)) 1075 { 1076 GNUNET_break_op (0); 1077 return GNUNET_SYSERR; 1078 } 1079 len = json_array_size (history); 1080 { 1081 struct TALER_EXCHANGE_ReserveHistoryEntry *rhistory; 1082 1083 rhistory = GNUNET_new_array (len, 1084 struct TALER_EXCHANGE_ReserveHistoryEntry); 1085 if (GNUNET_OK != 1086 parse_reserve_history (grhh->keys, 1087 history, 1088 &grhh->reserve_pub, 1089 rs.details.ok.balance.currency, 1090 &rs.details.ok.total_in, 1091 &rs.details.ok.total_out, 1092 len, 1093 rhistory)) 1094 { 1095 GNUNET_break_op (0); 1096 free_reserve_history (len, 1097 rhistory); 1098 GNUNET_JSON_parse_free (spec); 1099 return GNUNET_SYSERR; 1100 } 1101 if (NULL != grhh->cb) 1102 { 1103 rs.details.ok.history = rhistory; 1104 rs.details.ok.history_len = len; 1105 grhh->cb (grhh->cb_cls, 1106 &rs); 1107 grhh->cb = NULL; 1108 } 1109 free_reserve_history (len, 1110 rhistory); 1111 } 1112 return GNUNET_OK; 1113 } 1114 1115 1116 /** 1117 * Function called when we're done processing the 1118 * HTTP GET /reserves/$RESERVE_PUB/history request. 1119 * 1120 * @param cls the `struct TALER_EXCHANGE_GetReservesHistoryHandle` 1121 * @param response_code HTTP response code, 0 on error 1122 * @param response parsed JSON result, NULL on error 1123 */ 1124 static void 1125 handle_reserves_history_finished (void *cls, 1126 long response_code, 1127 const void *response) 1128 { 1129 struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh = cls; 1130 const json_t *j = response; 1131 struct TALER_EXCHANGE_GetReservesHistoryResponse rs = { 1132 .hr.reply = j, 1133 .hr.http_status = (unsigned int) response_code 1134 }; 1135 1136 grhh->job = NULL; 1137 switch (response_code) 1138 { 1139 case 0: 1140 rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 1141 break; 1142 case MHD_HTTP_OK: 1143 if (GNUNET_OK != 1144 handle_reserves_history_ok (grhh, 1145 j)) 1146 { 1147 rs.hr.http_status = 0; 1148 rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 1149 } 1150 break; 1151 case MHD_HTTP_BAD_REQUEST: 1152 /* This should never happen, either us or the exchange is buggy 1153 (or API version conflict); just pass JSON reply to the application */ 1154 GNUNET_break (0); 1155 rs.hr.ec = TALER_JSON_get_error_code (j); 1156 rs.hr.hint = TALER_JSON_get_error_hint (j); 1157 break; 1158 case MHD_HTTP_FORBIDDEN: 1159 /* This should never happen, either us or the exchange is buggy 1160 (or API version conflict); just pass JSON reply to the application */ 1161 GNUNET_break (0); 1162 rs.hr.ec = TALER_JSON_get_error_code (j); 1163 rs.hr.hint = TALER_JSON_get_error_hint (j); 1164 break; 1165 case MHD_HTTP_NOT_FOUND: 1166 /* Nothing really to verify, this should never 1167 happen, we should pass the JSON reply to the application */ 1168 rs.hr.ec = TALER_JSON_get_error_code (j); 1169 rs.hr.hint = TALER_JSON_get_error_hint (j); 1170 break; 1171 case MHD_HTTP_INTERNAL_SERVER_ERROR: 1172 /* Server had an internal issue; we should retry, but this API 1173 leaves this to the application */ 1174 rs.hr.ec = TALER_JSON_get_error_code (j); 1175 rs.hr.hint = TALER_JSON_get_error_hint (j); 1176 break; 1177 default: 1178 /* unexpected response code */ 1179 GNUNET_break_op (0); 1180 rs.hr.ec = TALER_JSON_get_error_code (j); 1181 rs.hr.hint = TALER_JSON_get_error_hint (j); 1182 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1183 "Unexpected response code %u/%d for reserves history\n", 1184 (unsigned int) response_code, 1185 (int) rs.hr.ec); 1186 break; 1187 } 1188 if (NULL != grhh->cb) 1189 { 1190 grhh->cb (grhh->cb_cls, 1191 &rs); 1192 grhh->cb = NULL; 1193 } 1194 TALER_EXCHANGE_get_reserves_history_cancel (grhh); 1195 } 1196 1197 1198 struct TALER_EXCHANGE_GetReservesHistoryHandle * 1199 TALER_EXCHANGE_get_reserves_history_create ( 1200 struct GNUNET_CURL_Context *ctx, 1201 const char *url, 1202 struct TALER_EXCHANGE_Keys *keys, 1203 const struct TALER_ReservePrivateKeyP *reserve_priv) 1204 { 1205 struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh; 1206 1207 grhh = GNUNET_new (struct TALER_EXCHANGE_GetReservesHistoryHandle); 1208 grhh->ctx = ctx; 1209 grhh->base_url = GNUNET_strdup (url); 1210 grhh->keys = TALER_EXCHANGE_keys_incref (keys); 1211 grhh->reserve_priv = *reserve_priv; 1212 GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, 1213 &grhh->reserve_pub.eddsa_pub); 1214 return grhh; 1215 } 1216 1217 1218 enum GNUNET_GenericReturnValue 1219 TALER_EXCHANGE_get_reserves_history_set_options_ ( 1220 struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh, 1221 unsigned int num_options, 1222 const struct TALER_EXCHANGE_GetReservesHistoryOptionValue *options) 1223 { 1224 for (unsigned int i = 0; i < num_options; i++) 1225 { 1226 switch (options[i].option) 1227 { 1228 case TALER_EXCHANGE_GET_RESERVES_HISTORY_OPTION_END: 1229 return GNUNET_OK; 1230 case TALER_EXCHANGE_GET_RESERVES_HISTORY_OPTION_START_OFF: 1231 grhh->options.start_off = options[i].details.start_off; 1232 break; 1233 default: 1234 GNUNET_break (0); 1235 return GNUNET_SYSERR; 1236 } 1237 } 1238 return GNUNET_OK; 1239 } 1240 1241 1242 enum TALER_ErrorCode 1243 TALER_EXCHANGE_get_reserves_history_start ( 1244 struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh, 1245 TALER_EXCHANGE_GetReservesHistoryCallback cb, 1246 TALER_EXCHANGE_GET_RESERVES_HISTORY_RESULT_CLOSURE *cb_cls) 1247 { 1248 char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32]; 1249 struct curl_slist *job_headers; 1250 CURL *eh; 1251 1252 if (NULL != grhh->job) 1253 { 1254 GNUNET_break (0); 1255 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 1256 } 1257 grhh->cb = cb; 1258 grhh->cb_cls = cb_cls; 1259 { 1260 char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2]; 1261 char *end; 1262 char start_off_str[32]; 1263 1264 end = GNUNET_STRINGS_data_to_string ( 1265 &grhh->reserve_pub, 1266 sizeof (grhh->reserve_pub), 1267 pub_str, 1268 sizeof (pub_str)); 1269 *end = '\0'; 1270 GNUNET_snprintf (arg_str, 1271 sizeof (arg_str), 1272 "reserves/%s/history", 1273 pub_str); 1274 GNUNET_snprintf (start_off_str, 1275 sizeof (start_off_str), 1276 "%llu", 1277 (unsigned long long) grhh->options.start_off); 1278 grhh->url = TALER_url_join (grhh->base_url, 1279 arg_str, 1280 "start", 1281 (0 != grhh->options.start_off) 1282 ? start_off_str 1283 : NULL, 1284 NULL); 1285 } 1286 if (NULL == grhh->url) 1287 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 1288 eh = TALER_EXCHANGE_curl_easy_get_ (grhh->url); 1289 if (NULL == eh) 1290 { 1291 GNUNET_free (grhh->url); 1292 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 1293 } 1294 GNUNET_assert (CURLE_OK == 1295 curl_easy_setopt (eh, 1296 CURLOPT_HEADERFUNCTION, 1297 &handle_header)); 1298 GNUNET_assert (CURLE_OK == 1299 curl_easy_setopt (eh, 1300 CURLOPT_HEADERDATA, 1301 grhh)); 1302 { 1303 struct TALER_ReserveSignatureP reserve_sig; 1304 char *sig_hdr; 1305 char *hdr; 1306 1307 TALER_wallet_reserve_history_sign (grhh->options.start_off, 1308 &grhh->reserve_priv, 1309 &reserve_sig); 1310 sig_hdr = GNUNET_STRINGS_data_to_string_alloc ( 1311 &reserve_sig, 1312 sizeof (reserve_sig)); 1313 GNUNET_asprintf (&hdr, 1314 "%s: %s", 1315 TALER_RESERVE_HISTORY_SIGNATURE_HEADER, 1316 sig_hdr); 1317 GNUNET_free (sig_hdr); 1318 job_headers = curl_slist_append (NULL, 1319 hdr); 1320 GNUNET_free (hdr); 1321 if (NULL == job_headers) 1322 { 1323 GNUNET_break (0); 1324 curl_easy_cleanup (eh); 1325 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 1326 } 1327 } 1328 grhh->job = GNUNET_CURL_job_add2 (grhh->ctx, 1329 eh, 1330 job_headers, 1331 &handle_reserves_history_finished, 1332 grhh); 1333 curl_slist_free_all (job_headers); 1334 if (NULL == grhh->job) 1335 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 1336 return TALER_EC_NONE; 1337 } 1338 1339 1340 void 1341 TALER_EXCHANGE_get_reserves_history_cancel ( 1342 struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh) 1343 { 1344 if (NULL != grhh->job) 1345 { 1346 GNUNET_CURL_job_cancel (grhh->job); 1347 grhh->job = NULL; 1348 } 1349 GNUNET_free (grhh->url); 1350 GNUNET_free (grhh->base_url); 1351 TALER_EXCHANGE_keys_decref (grhh->keys); 1352 GNUNET_free (grhh); 1353 } 1354 1355 1356 /* end of exchange_api_get-reserves-RESERVE_PUB-history.c */