exchange_api_get-reserves-RESERVE_PUB-history.c (39983B)
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 rh->details.withdraw.selected_h = selected_h; 329 rh->details.withdraw.noreveal_index = noreveal_index; 330 rh->details.withdraw.no_blinding_seed = no_blinding_seed; 331 rh->details.withdraw.reserve_sig = reserve_sig; 332 if (! no_blinding_seed) 333 rh->details.withdraw.blinding_seed = blinding_seed; 334 335 /* check that withdraw fee matches expectations! */ 336 { 337 const struct TALER_EXCHANGE_Keys *key_state; 338 struct TALER_Amount fee_acc; 339 struct TALER_Amount amount_acc; 340 341 GNUNET_assert (GNUNET_OK == 342 TALER_amount_set_zero (withdraw_amount.currency, 343 &fee_acc)); 344 GNUNET_assert (GNUNET_OK == 345 TALER_amount_set_zero (withdraw_amount.currency, 346 &amount_acc)); 347 348 key_state = uc->keys; 349 350 /* accumulate the withdraw fees */ 351 for (size_t i=0; i < num_coins; i++) 352 { 353 const struct TALER_EXCHANGE_DenomPublicKey *dki; 354 355 dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state, 356 &denom_pub_hashes[i]); 357 if (NULL == dki) 358 { 359 GNUNET_break_op (0); 360 GNUNET_JSON_parse_free (withdraw_spec); 361 return GNUNET_SYSERR; 362 } 363 if ( (0 > 364 TALER_amount_add (&fee_acc, 365 &fee_acc, 366 &dki->fees.withdraw)) || 367 (0 > 368 TALER_amount_add (&amount_acc, 369 &amount_acc, 370 &dki->value)) ) 371 { 372 GNUNET_break_op (0); 373 GNUNET_JSON_parse_free (withdraw_spec); 374 return GNUNET_SYSERR; 375 } 376 } 377 378 if ( (GNUNET_YES != 379 TALER_amount_cmp_currency (&fee_acc, 380 &withdraw_fee)) || 381 (0 != 382 TALER_amount_cmp (&amount_acc, 383 &amount_without_fee)) ) 384 { 385 GNUNET_break_op (0); 386 GNUNET_JSON_parse_free (withdraw_spec); 387 return GNUNET_SYSERR; 388 } 389 rh->details.withdraw.fee = withdraw_fee; 390 } 391 392 /* Check check that the same withdraw transaction 393 isn't listed twice by the exchange. We use the 394 "uuid" array to remember the hashes of all 395 signatures, and compare the hashes to find 396 duplicates. */ 397 GNUNET_CRYPTO_hash (&reserve_sig, 398 sizeof (reserve_sig), 399 &uc->uuids[uc->uuid_off]); 400 for (unsigned int i = 0; i<uc->uuid_off; i++) 401 { 402 if (0 == GNUNET_memcmp (&uc->uuids[uc->uuid_off], 403 &uc->uuids[i])) 404 { 405 GNUNET_break_op (0); 406 GNUNET_JSON_parse_free (withdraw_spec); 407 return GNUNET_SYSERR; 408 } 409 } 410 uc->uuid_off++; 411 412 if (0 > 413 TALER_amount_add (uc->total_out, 414 uc->total_out, 415 &rh->amount)) 416 { 417 /* overflow in history already!? inconceivable! Bad exchange! */ 418 GNUNET_break_op (0); 419 GNUNET_JSON_parse_free (withdraw_spec); 420 return GNUNET_SYSERR; 421 } 422 GNUNET_JSON_parse_free (withdraw_spec); 423 return GNUNET_OK; 424 } 425 426 427 /** 428 * Parse "recoup" reserve history entry. 429 * 430 * @param[in,out] rh entry to parse 431 * @param uc our context 432 * @param transaction the transaction to parse 433 * @return #GNUNET_OK on success 434 */ 435 static enum GNUNET_GenericReturnValue 436 parse_recoup (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, 437 struct HistoryParseContext *uc, 438 const json_t *transaction) 439 { 440 const struct TALER_EXCHANGE_Keys *key_state; 441 struct GNUNET_JSON_Specification recoup_spec[] = { 442 GNUNET_JSON_spec_fixed_auto ("coin_pub", 443 &rh->details.recoup_details.coin_pub), 444 GNUNET_JSON_spec_fixed_auto ("exchange_sig", 445 &rh->details.recoup_details.exchange_sig), 446 GNUNET_JSON_spec_fixed_auto ("exchange_pub", 447 &rh->details.recoup_details.exchange_pub), 448 GNUNET_JSON_spec_timestamp ("timestamp", 449 &rh->details.recoup_details.timestamp), 450 GNUNET_JSON_spec_fixed_auto ("planchets_h", 451 &rh->details.recoup_details.planchets_h), 452 GNUNET_JSON_spec_end () 453 }; 454 455 rh->type = TALER_EXCHANGE_RTT_RECOUP; 456 if (GNUNET_OK != 457 GNUNET_JSON_parse (transaction, 458 recoup_spec, 459 NULL, NULL)) 460 { 461 GNUNET_break_op (0); 462 return GNUNET_SYSERR; 463 } 464 key_state = uc->keys; 465 if (GNUNET_OK != 466 TALER_EXCHANGE_test_signing_key (key_state, 467 &rh->details. 468 recoup_details.exchange_pub)) 469 { 470 GNUNET_break_op (0); 471 return GNUNET_SYSERR; 472 } 473 if (GNUNET_OK != 474 TALER_exchange_online_confirm_recoup_verify ( 475 rh->details.recoup_details.timestamp, 476 &rh->amount, 477 &rh->details.recoup_details.coin_pub, 478 uc->reserve_pub, 479 &rh->details.recoup_details.exchange_pub, 480 &rh->details.recoup_details.exchange_sig)) 481 { 482 GNUNET_break_op (0); 483 return GNUNET_SYSERR; 484 } 485 if (0 > 486 TALER_amount_add (uc->total_in, 487 uc->total_in, 488 &rh->amount)) 489 { 490 /* overflow in history already!? inconceivable! Bad exchange! */ 491 GNUNET_break_op (0); 492 return GNUNET_SYSERR; 493 } 494 return GNUNET_OK; 495 } 496 497 498 /** 499 * Parse "closing" reserve history entry. 500 * 501 * @param[in,out] rh entry to parse 502 * @param uc our context 503 * @param transaction the transaction to parse 504 * @return #GNUNET_OK on success 505 */ 506 static enum GNUNET_GenericReturnValue 507 parse_closing (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, 508 struct HistoryParseContext *uc, 509 const json_t *transaction) 510 { 511 const struct TALER_EXCHANGE_Keys *key_state; 512 struct TALER_FullPayto receiver_uri; 513 struct GNUNET_JSON_Specification closing_spec[] = { 514 TALER_JSON_spec_full_payto_uri ( 515 "receiver_account_details", 516 &receiver_uri), 517 GNUNET_JSON_spec_fixed_auto ("wtid", 518 &rh->details.close_details.wtid), 519 GNUNET_JSON_spec_fixed_auto ("exchange_sig", 520 &rh->details.close_details.exchange_sig), 521 GNUNET_JSON_spec_fixed_auto ("exchange_pub", 522 &rh->details.close_details.exchange_pub), 523 TALER_JSON_spec_amount_any ("closing_fee", 524 &rh->details.close_details.fee), 525 GNUNET_JSON_spec_timestamp ("timestamp", 526 &rh->details.close_details.timestamp), 527 GNUNET_JSON_spec_end () 528 }; 529 530 rh->type = TALER_EXCHANGE_RTT_CLOSING; 531 if (GNUNET_OK != 532 GNUNET_JSON_parse (transaction, 533 closing_spec, 534 NULL, NULL)) 535 { 536 GNUNET_break_op (0); 537 return GNUNET_SYSERR; 538 } 539 key_state = uc->keys; 540 if (GNUNET_OK != 541 TALER_EXCHANGE_test_signing_key ( 542 key_state, 543 &rh->details.close_details.exchange_pub)) 544 { 545 GNUNET_break_op (0); 546 return GNUNET_SYSERR; 547 } 548 if (GNUNET_OK != 549 TALER_exchange_online_reserve_closed_verify ( 550 rh->details.close_details.timestamp, 551 &rh->amount, 552 &rh->details.close_details.fee, 553 receiver_uri, 554 &rh->details.close_details.wtid, 555 uc->reserve_pub, 556 &rh->details.close_details.exchange_pub, 557 &rh->details.close_details.exchange_sig)) 558 { 559 GNUNET_break_op (0); 560 return GNUNET_SYSERR; 561 } 562 if (0 > 563 TALER_amount_add (uc->total_out, 564 uc->total_out, 565 &rh->amount)) 566 { 567 /* overflow in history already!? inconceivable! Bad exchange! */ 568 GNUNET_break_op (0); 569 return GNUNET_SYSERR; 570 } 571 rh->details.close_details.receiver_account_details.full_payto 572 = GNUNET_strdup (receiver_uri.full_payto); 573 return GNUNET_OK; 574 } 575 576 577 /** 578 * Parse "merge" reserve history entry. 579 * 580 * @param[in,out] rh entry to parse 581 * @param uc our context 582 * @param transaction the transaction to parse 583 * @return #GNUNET_OK on success 584 */ 585 static enum GNUNET_GenericReturnValue 586 parse_merge (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, 587 struct HistoryParseContext *uc, 588 const json_t *transaction) 589 { 590 uint32_t flags32; 591 struct GNUNET_JSON_Specification merge_spec[] = { 592 GNUNET_JSON_spec_fixed_auto ("h_contract_terms", 593 &rh->details.merge_details.h_contract_terms), 594 GNUNET_JSON_spec_fixed_auto ("merge_pub", 595 &rh->details.merge_details.merge_pub), 596 GNUNET_JSON_spec_fixed_auto ("purse_pub", 597 &rh->details.merge_details.purse_pub), 598 GNUNET_JSON_spec_uint32 ("min_age", 599 &rh->details.merge_details.min_age), 600 GNUNET_JSON_spec_uint32 ("flags", 601 &flags32), 602 GNUNET_JSON_spec_fixed_auto ("reserve_sig", 603 &rh->details.merge_details.reserve_sig), 604 TALER_JSON_spec_amount_any ("purse_fee", 605 &rh->details.merge_details.purse_fee), 606 GNUNET_JSON_spec_timestamp ("merge_timestamp", 607 &rh->details.merge_details.merge_timestamp), 608 GNUNET_JSON_spec_timestamp ("purse_expiration", 609 &rh->details.merge_details.purse_expiration), 610 GNUNET_JSON_spec_bool ("merged", 611 &rh->details.merge_details.merged), 612 GNUNET_JSON_spec_end () 613 }; 614 615 rh->type = TALER_EXCHANGE_RTT_MERGE; 616 if (GNUNET_OK != 617 GNUNET_JSON_parse (transaction, 618 merge_spec, 619 NULL, NULL)) 620 { 621 GNUNET_break_op (0); 622 return GNUNET_SYSERR; 623 } 624 rh->details.merge_details.flags = 625 (enum TALER_WalletAccountMergeFlags) flags32; 626 if (GNUNET_OK != 627 TALER_wallet_account_merge_verify ( 628 rh->details.merge_details.merge_timestamp, 629 &rh->details.merge_details.purse_pub, 630 rh->details.merge_details.purse_expiration, 631 &rh->details.merge_details.h_contract_terms, 632 &rh->amount, 633 &rh->details.merge_details.purse_fee, 634 rh->details.merge_details.min_age, 635 rh->details.merge_details.flags, 636 uc->reserve_pub, 637 &rh->details.merge_details.reserve_sig)) 638 { 639 GNUNET_break_op (0); 640 return GNUNET_SYSERR; 641 } 642 if (rh->details.merge_details.merged) 643 { 644 if (0 > 645 TALER_amount_add (uc->total_in, 646 uc->total_in, 647 &rh->amount)) 648 { 649 /* overflow in history already!? inconceivable! Bad exchange! */ 650 GNUNET_break_op (0); 651 return GNUNET_SYSERR; 652 } 653 } 654 else 655 { 656 if (0 > 657 TALER_amount_add (uc->total_out, 658 uc->total_out, 659 &rh->details.merge_details.purse_fee)) 660 { 661 /* overflow in history already!? inconceivable! Bad exchange! */ 662 GNUNET_break_op (0); 663 return GNUNET_SYSERR; 664 } 665 } 666 return GNUNET_OK; 667 } 668 669 670 /** 671 * Parse "open" reserve open entry. 672 * 673 * @param[in,out] rh entry to parse 674 * @param uc our context 675 * @param transaction the transaction to parse 676 * @return #GNUNET_OK on success 677 */ 678 static enum GNUNET_GenericReturnValue 679 parse_open (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, 680 struct HistoryParseContext *uc, 681 const json_t *transaction) 682 { 683 struct GNUNET_JSON_Specification open_spec[] = { 684 GNUNET_JSON_spec_fixed_auto ("reserve_sig", 685 &rh->details.open_request.reserve_sig), 686 TALER_JSON_spec_amount_any ("open_fee", 687 &rh->details.open_request.reserve_payment), 688 GNUNET_JSON_spec_uint32 ("requested_min_purses", 689 &rh->details.open_request.purse_limit), 690 GNUNET_JSON_spec_timestamp ("request_timestamp", 691 &rh->details.open_request.request_timestamp), 692 GNUNET_JSON_spec_timestamp ("requested_expiration", 693 &rh->details.open_request.reserve_expiration), 694 GNUNET_JSON_spec_end () 695 }; 696 697 rh->type = TALER_EXCHANGE_RTT_OPEN; 698 if (GNUNET_OK != 699 GNUNET_JSON_parse (transaction, 700 open_spec, 701 NULL, NULL)) 702 { 703 GNUNET_break_op (0); 704 return GNUNET_SYSERR; 705 } 706 /* the entry has no "amount" of its own; the amount debited from 707 the reserve is the reserve payment for the open request */ 708 rh->amount = rh->details.open_request.reserve_payment; 709 if (GNUNET_OK != 710 TALER_wallet_reserve_open_verify ( 711 &rh->details.open_request.reserve_payment, 712 rh->details.open_request.request_timestamp, 713 rh->details.open_request.reserve_expiration, 714 rh->details.open_request.purse_limit, 715 uc->reserve_pub, 716 &rh->details.open_request.reserve_sig)) 717 { 718 GNUNET_break_op (0); 719 return GNUNET_SYSERR; 720 } 721 if (0 > 722 TALER_amount_add (uc->total_out, 723 uc->total_out, 724 &rh->details.open_request.reserve_payment)) 725 { 726 /* overflow in history already!? inconceivable! Bad exchange! */ 727 GNUNET_break_op (0); 728 return GNUNET_SYSERR; 729 } 730 return GNUNET_OK; 731 } 732 733 734 /** 735 * Parse "close" reserve close entry. 736 * 737 * @param[in,out] rh entry to parse 738 * @param uc our context 739 * @param transaction the transaction to parse 740 * @return #GNUNET_OK on success 741 */ 742 static enum GNUNET_GenericReturnValue 743 parse_close (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, 744 struct HistoryParseContext *uc, 745 const json_t *transaction) 746 { 747 struct GNUNET_JSON_Specification close_spec[] = { 748 GNUNET_JSON_spec_fixed_auto ("reserve_sig", 749 &rh->details.close_request.reserve_sig), 750 GNUNET_JSON_spec_mark_optional ( 751 GNUNET_JSON_spec_fixed_auto ("h_payto", 752 &rh->details.close_request. 753 target_account_h_payto), 754 NULL), 755 GNUNET_JSON_spec_timestamp ("request_timestamp", 756 &rh->details.close_request.request_timestamp), 757 GNUNET_JSON_spec_end () 758 }; 759 760 rh->type = TALER_EXCHANGE_RTT_CLOSE; 761 if (GNUNET_OK != 762 GNUNET_JSON_parse (transaction, 763 close_spec, 764 NULL, NULL)) 765 { 766 GNUNET_break_op (0); 767 return GNUNET_SYSERR; 768 } 769 /* force amount to invalid */ 770 memset (&rh->amount, 771 0, 772 sizeof (rh->amount)); 773 if (GNUNET_OK != 774 TALER_wallet_reserve_close_verify ( 775 rh->details.close_request.request_timestamp, 776 &rh->details.close_request.target_account_h_payto, 777 uc->reserve_pub, 778 &rh->details.close_request.reserve_sig)) 779 { 780 GNUNET_break_op (0); 781 return GNUNET_SYSERR; 782 } 783 return GNUNET_OK; 784 } 785 786 787 static void 788 free_reserve_history ( 789 unsigned int len, 790 struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static len]) 791 { 792 for (unsigned int i = 0; i<len; i++) 793 { 794 struct TALER_EXCHANGE_ReserveHistoryEntry *rhi = &rhistory[i]; 795 796 switch (rhi->type) 797 { 798 case TALER_EXCHANGE_RTT_CREDIT: 799 GNUNET_free (rhi->details.in_details.sender_url.full_payto); 800 break; 801 case TALER_EXCHANGE_RTT_WITHDRAWAL: 802 break; 803 case TALER_EXCHANGE_RTT_RECOUP: 804 break; 805 case TALER_EXCHANGE_RTT_CLOSING: 806 GNUNET_free ( 807 rhi->details.close_details.receiver_account_details.full_payto); 808 break; 809 case TALER_EXCHANGE_RTT_MERGE: 810 break; 811 case TALER_EXCHANGE_RTT_OPEN: 812 break; 813 case TALER_EXCHANGE_RTT_CLOSE: 814 break; 815 } 816 } 817 GNUNET_free (rhistory); 818 } 819 820 821 /** 822 * Parse history given in JSON format and return it in binary format. 823 * 824 * @param keys exchange keys 825 * @param history JSON array with the history 826 * @param reserve_pub public key of the reserve to inspect 827 * @param currency currency we expect the balance to be in 828 * @param[out] total_in set to value of credits to reserve 829 * @param[out] total_out set to value of debits from reserve 830 * @param history_length number of entries in @a history 831 * @param[out] rhistory array of length @a history_length, set to the 832 * parsed history entries 833 * @return #GNUNET_OK if history was valid and @a rhistory and @a balance 834 * were set, 835 * #GNUNET_SYSERR if there was a protocol violation in @a history 836 */ 837 static enum GNUNET_GenericReturnValue 838 parse_reserve_history ( 839 const struct TALER_EXCHANGE_Keys *keys, 840 const json_t *history, 841 const struct TALER_ReservePublicKeyP *reserve_pub, 842 const char *currency, 843 struct TALER_Amount *total_in, 844 struct TALER_Amount *total_out, 845 unsigned int history_length, 846 struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static history_length]) 847 { 848 const struct 849 { 850 const char *type; 851 ParseHelper helper; 852 } map[] = { 853 { "CREDIT", &parse_credit }, 854 { "WITHDRAW", &parse_withdraw }, 855 { "RECOUP", &parse_recoup }, 856 { "MERGE", &parse_merge }, 857 { "CLOSING", &parse_closing }, 858 { "OPEN", &parse_open }, 859 { "CLOSE", &parse_close }, 860 { NULL, NULL } 861 }; 862 struct GNUNET_HashCode *uuid 863 = GNUNET_new_array (history_length, 864 struct GNUNET_HashCode); 865 struct HistoryParseContext uc = { 866 .keys = keys, 867 .reserve_pub = reserve_pub, 868 .uuids = uuid, 869 .total_in = total_in, 870 .total_out = total_out 871 }; 872 873 GNUNET_assert (GNUNET_OK == 874 TALER_amount_set_zero (currency, 875 total_in)); 876 GNUNET_assert (GNUNET_OK == 877 TALER_amount_set_zero (currency, 878 total_out)); 879 for (unsigned int off = 0; off<history_length; off++) 880 { 881 struct TALER_EXCHANGE_ReserveHistoryEntry *rh = &rhistory[off]; 882 json_t *transaction; 883 const char *type; 884 bool no_amount = false; 885 struct GNUNET_JSON_Specification hist_spec[] = { 886 GNUNET_JSON_spec_string ("type", 887 &type), 888 /* 'OPEN' and 'CLOSE' entries have no 'amount'! */ 889 GNUNET_JSON_spec_mark_optional ( 890 TALER_JSON_spec_amount_any ("amount", 891 &rh->amount), 892 &no_amount), 893 GNUNET_JSON_spec_uint64 ("history_offset", 894 &rh->history_offset), 895 /* 'wire' and 'signature' are optional depending on 'type'! */ 896 GNUNET_JSON_spec_end () 897 }; 898 bool found = false; 899 900 transaction = json_array_get (history, 901 off); 902 if (GNUNET_OK != 903 GNUNET_JSON_parse (transaction, 904 hist_spec, 905 NULL, NULL)) 906 { 907 GNUNET_break_op (0); 908 #if DEBUG 909 json_dumpf (transaction, 910 stderr, 911 JSON_INDENT (2)); 912 #endif 913 GNUNET_free (uuid); 914 return GNUNET_SYSERR; 915 } 916 if (no_amount) 917 { 918 /* only 'OPEN' and 'CLOSE' entries may omit the amount */ 919 if ( (0 != strcasecmp ("OPEN", 920 type)) && 921 (0 != strcasecmp ("CLOSE", 922 type)) ) 923 { 924 GNUNET_break_op (0); 925 GNUNET_free (uuid); 926 return GNUNET_SYSERR; 927 } 928 memset (&rh->amount, 929 0, 930 sizeof (rh->amount)); 931 } 932 else if (GNUNET_YES != 933 TALER_amount_cmp_currency (&rh->amount, 934 total_in)) 935 { 936 GNUNET_break_op (0); 937 GNUNET_free (uuid); 938 return GNUNET_SYSERR; 939 } 940 for (unsigned int i = 0; NULL != map[i].type; i++) 941 { 942 if (0 == strcasecmp (map[i].type, 943 type)) 944 { 945 found = true; 946 if (GNUNET_OK != 947 map[i].helper (rh, 948 &uc, 949 transaction)) 950 { 951 GNUNET_break_op (0); 952 GNUNET_free (uuid); 953 return GNUNET_SYSERR; 954 } 955 break; 956 } 957 } 958 if (! found) 959 { 960 /* unexpected 'type', protocol incompatibility, complain! */ 961 GNUNET_break_op (0); 962 GNUNET_free (uuid); 963 return GNUNET_SYSERR; 964 } 965 } 966 GNUNET_free (uuid); 967 return GNUNET_OK; 968 } 969 970 971 /** 972 * Handle HTTP header received by curl. 973 * 974 * @param buffer one line of HTTP header data 975 * @param size size of an item 976 * @param nitems number of items passed 977 * @param userdata our `struct TALER_EXCHANGE_GetReservesHistoryHandle *` 978 * @return `size * nitems` 979 */ 980 static size_t 981 handle_header (char *buffer, 982 size_t size, 983 size_t nitems, 984 void *userdata) 985 { 986 struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh = userdata; 987 size_t total = size * nitems; 988 char *ndup; 989 const char *hdr_type; 990 char *hdr_val; 991 char *sp; 992 993 ndup = GNUNET_strndup (buffer, 994 total); 995 hdr_type = strtok_r (ndup, 996 ":", 997 &sp); 998 if (NULL == hdr_type) 999 { 1000 GNUNET_free (ndup); 1001 return total; 1002 } 1003 hdr_val = strtok_r (NULL, 1004 "\n\r", 1005 &sp); 1006 if (NULL == hdr_val) 1007 { 1008 GNUNET_free (ndup); 1009 return total; 1010 } 1011 if (' ' == *hdr_val) 1012 hdr_val++; 1013 if (0 == strcasecmp (hdr_type, 1014 MHD_HTTP_HEADER_ETAG)) 1015 { 1016 unsigned long long tval; 1017 char dummy; 1018 1019 if (1 != 1020 sscanf (hdr_val, 1021 "\"%llu\"%c", 1022 &tval, 1023 &dummy)) 1024 { 1025 GNUNET_break_op (0); 1026 GNUNET_free (ndup); 1027 return 0; 1028 } 1029 grhh->etag = (uint64_t) tval; 1030 } 1031 GNUNET_free (ndup); 1032 return total; 1033 } 1034 1035 1036 /** 1037 * We received an #MHD_HTTP_OK status code. Handle the JSON response. 1038 * 1039 * @param grhh handle of the request 1040 * @param j JSON response 1041 * @return #GNUNET_OK on success 1042 */ 1043 static enum GNUNET_GenericReturnValue 1044 handle_reserves_history_ok ( 1045 struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh, 1046 const json_t *j) 1047 { 1048 const json_t *history; 1049 unsigned int len; 1050 struct TALER_EXCHANGE_GetReservesHistoryResponse rs = { 1051 .hr.reply = j, 1052 .hr.http_status = MHD_HTTP_OK, 1053 .details.ok.etag = grhh->etag 1054 }; 1055 struct GNUNET_JSON_Specification spec[] = { 1056 TALER_JSON_spec_amount_any ("balance", 1057 &rs.details.ok.balance), 1058 GNUNET_JSON_spec_array_const ("history", 1059 &history), 1060 GNUNET_JSON_spec_end () 1061 }; 1062 1063 if (GNUNET_OK != 1064 GNUNET_JSON_parse (j, 1065 spec, 1066 NULL, 1067 NULL)) 1068 { 1069 GNUNET_break_op (0); 1070 return GNUNET_SYSERR; 1071 } 1072 len = json_array_size (history); 1073 { 1074 struct TALER_EXCHANGE_ReserveHistoryEntry *rhistory; 1075 1076 rhistory = GNUNET_new_array (len, 1077 struct TALER_EXCHANGE_ReserveHistoryEntry); 1078 if (GNUNET_OK != 1079 parse_reserve_history (grhh->keys, 1080 history, 1081 &grhh->reserve_pub, 1082 rs.details.ok.balance.currency, 1083 &rs.details.ok.total_in, 1084 &rs.details.ok.total_out, 1085 len, 1086 rhistory)) 1087 { 1088 GNUNET_break_op (0); 1089 free_reserve_history (len, 1090 rhistory); 1091 GNUNET_JSON_parse_free (spec); 1092 return GNUNET_SYSERR; 1093 } 1094 if (NULL != grhh->cb) 1095 { 1096 rs.details.ok.history = rhistory; 1097 rs.details.ok.history_len = len; 1098 grhh->cb (grhh->cb_cls, 1099 &rs); 1100 grhh->cb = NULL; 1101 } 1102 free_reserve_history (len, 1103 rhistory); 1104 } 1105 return GNUNET_OK; 1106 } 1107 1108 1109 /** 1110 * Function called when we're done processing the 1111 * HTTP GET /reserves/$RESERVE_PUB/history request. 1112 * 1113 * @param cls the `struct TALER_EXCHANGE_GetReservesHistoryHandle` 1114 * @param response_code HTTP response code, 0 on error 1115 * @param response parsed JSON result, NULL on error 1116 */ 1117 static void 1118 handle_reserves_history_finished (void *cls, 1119 long response_code, 1120 const void *response) 1121 { 1122 struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh = cls; 1123 const json_t *j = response; 1124 struct TALER_EXCHANGE_GetReservesHistoryResponse rs = { 1125 .hr.reply = j, 1126 .hr.http_status = (unsigned int) response_code 1127 }; 1128 1129 grhh->job = NULL; 1130 switch (response_code) 1131 { 1132 case 0: 1133 rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 1134 break; 1135 case MHD_HTTP_OK: 1136 if (GNUNET_OK != 1137 handle_reserves_history_ok (grhh, 1138 j)) 1139 { 1140 rs.hr.http_status = 0; 1141 rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 1142 } 1143 break; 1144 case MHD_HTTP_BAD_REQUEST: 1145 /* This should never happen, either us or the exchange is buggy 1146 (or API version conflict); just pass JSON reply to the application */ 1147 GNUNET_break (0); 1148 rs.hr.ec = TALER_JSON_get_error_code (j); 1149 rs.hr.hint = TALER_JSON_get_error_hint (j); 1150 break; 1151 case MHD_HTTP_FORBIDDEN: 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_NOT_FOUND: 1159 /* Nothing really to verify, this should never 1160 happen, we should pass the JSON reply to the application */ 1161 rs.hr.ec = TALER_JSON_get_error_code (j); 1162 rs.hr.hint = TALER_JSON_get_error_hint (j); 1163 break; 1164 case MHD_HTTP_INTERNAL_SERVER_ERROR: 1165 /* Server had an internal issue; we should retry, but this API 1166 leaves this to the application */ 1167 rs.hr.ec = TALER_JSON_get_error_code (j); 1168 rs.hr.hint = TALER_JSON_get_error_hint (j); 1169 break; 1170 default: 1171 /* unexpected response code */ 1172 GNUNET_break_op (0); 1173 rs.hr.ec = TALER_JSON_get_error_code (j); 1174 rs.hr.hint = TALER_JSON_get_error_hint (j); 1175 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1176 "Unexpected response code %u/%d for reserves history\n", 1177 (unsigned int) response_code, 1178 (int) rs.hr.ec); 1179 break; 1180 } 1181 if (NULL != grhh->cb) 1182 { 1183 grhh->cb (grhh->cb_cls, 1184 &rs); 1185 grhh->cb = NULL; 1186 } 1187 TALER_EXCHANGE_get_reserves_history_cancel (grhh); 1188 } 1189 1190 1191 struct TALER_EXCHANGE_GetReservesHistoryHandle * 1192 TALER_EXCHANGE_get_reserves_history_create ( 1193 struct GNUNET_CURL_Context *ctx, 1194 const char *url, 1195 struct TALER_EXCHANGE_Keys *keys, 1196 const struct TALER_ReservePrivateKeyP *reserve_priv) 1197 { 1198 struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh; 1199 1200 grhh = GNUNET_new (struct TALER_EXCHANGE_GetReservesHistoryHandle); 1201 grhh->ctx = ctx; 1202 grhh->base_url = GNUNET_strdup (url); 1203 grhh->keys = TALER_EXCHANGE_keys_incref (keys); 1204 grhh->reserve_priv = *reserve_priv; 1205 GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, 1206 &grhh->reserve_pub.eddsa_pub); 1207 return grhh; 1208 } 1209 1210 1211 enum GNUNET_GenericReturnValue 1212 TALER_EXCHANGE_get_reserves_history_set_options_ ( 1213 struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh, 1214 unsigned int num_options, 1215 const struct TALER_EXCHANGE_GetReservesHistoryOptionValue *options) 1216 { 1217 for (unsigned int i = 0; i < num_options; i++) 1218 { 1219 switch (options[i].option) 1220 { 1221 case TALER_EXCHANGE_GET_RESERVES_HISTORY_OPTION_END: 1222 return GNUNET_OK; 1223 case TALER_EXCHANGE_GET_RESERVES_HISTORY_OPTION_START_OFF: 1224 grhh->options.start_off = options[i].details.start_off; 1225 break; 1226 default: 1227 GNUNET_break (0); 1228 return GNUNET_SYSERR; 1229 } 1230 } 1231 return GNUNET_OK; 1232 } 1233 1234 1235 enum TALER_ErrorCode 1236 TALER_EXCHANGE_get_reserves_history_start ( 1237 struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh, 1238 TALER_EXCHANGE_GetReservesHistoryCallback cb, 1239 TALER_EXCHANGE_GET_RESERVES_HISTORY_RESULT_CLOSURE *cb_cls) 1240 { 1241 char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32]; 1242 struct curl_slist *job_headers; 1243 CURL *eh; 1244 1245 if (NULL != grhh->job) 1246 { 1247 GNUNET_break (0); 1248 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 1249 } 1250 grhh->cb = cb; 1251 grhh->cb_cls = cb_cls; 1252 { 1253 char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2]; 1254 char *end; 1255 char start_off_str[32]; 1256 1257 end = GNUNET_STRINGS_data_to_string ( 1258 &grhh->reserve_pub, 1259 sizeof (grhh->reserve_pub), 1260 pub_str, 1261 sizeof (pub_str)); 1262 *end = '\0'; 1263 GNUNET_snprintf (arg_str, 1264 sizeof (arg_str), 1265 "reserves/%s/history", 1266 pub_str); 1267 GNUNET_snprintf (start_off_str, 1268 sizeof (start_off_str), 1269 "%llu", 1270 (unsigned long long) grhh->options.start_off); 1271 grhh->url = TALER_url_join (grhh->base_url, 1272 arg_str, 1273 "start", 1274 (0 != grhh->options.start_off) 1275 ? start_off_str 1276 : NULL, 1277 NULL); 1278 } 1279 if (NULL == grhh->url) 1280 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 1281 eh = TALER_EXCHANGE_curl_easy_get_ (grhh->url); 1282 if (NULL == eh) 1283 { 1284 GNUNET_free (grhh->url); 1285 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 1286 } 1287 GNUNET_assert (CURLE_OK == 1288 curl_easy_setopt (eh, 1289 CURLOPT_HEADERFUNCTION, 1290 &handle_header)); 1291 GNUNET_assert (CURLE_OK == 1292 curl_easy_setopt (eh, 1293 CURLOPT_HEADERDATA, 1294 grhh)); 1295 { 1296 struct TALER_ReserveSignatureP reserve_sig; 1297 char *sig_hdr; 1298 char *hdr; 1299 1300 TALER_wallet_reserve_history_sign (grhh->options.start_off, 1301 &grhh->reserve_priv, 1302 &reserve_sig); 1303 sig_hdr = GNUNET_STRINGS_data_to_string_alloc ( 1304 &reserve_sig, 1305 sizeof (reserve_sig)); 1306 GNUNET_asprintf (&hdr, 1307 "%s: %s", 1308 TALER_RESERVE_HISTORY_SIGNATURE_HEADER, 1309 sig_hdr); 1310 GNUNET_free (sig_hdr); 1311 job_headers = curl_slist_append (NULL, 1312 hdr); 1313 GNUNET_free (hdr); 1314 if (NULL == job_headers) 1315 { 1316 GNUNET_break (0); 1317 curl_easy_cleanup (eh); 1318 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 1319 } 1320 } 1321 grhh->job = GNUNET_CURL_job_add2 (grhh->ctx, 1322 eh, 1323 job_headers, 1324 &handle_reserves_history_finished, 1325 grhh); 1326 curl_slist_free_all (job_headers); 1327 if (NULL == grhh->job) 1328 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 1329 return TALER_EC_NONE; 1330 } 1331 1332 1333 void 1334 TALER_EXCHANGE_get_reserves_history_cancel ( 1335 struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh) 1336 { 1337 if (NULL != grhh->job) 1338 { 1339 GNUNET_CURL_job_cancel (grhh->job); 1340 grhh->job = NULL; 1341 } 1342 GNUNET_free (grhh->url); 1343 GNUNET_free (grhh->base_url); 1344 TALER_EXCHANGE_keys_decref (grhh->keys); 1345 GNUNET_free (grhh); 1346 } 1347 1348 1349 /* end of exchange_api_get-reserves-RESERVE_PUB-history.c */