taler-exchange-httpd_post-reserves-RESERVE_PUB-open.c (15597B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2022 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_post-reserves-RESERVE_PUB-open.c 18 * @brief Handle /reserves/$RESERVE_PUB/open requests 19 * @author Christian Grothoff 20 */ 21 #include <gnunet/gnunet_util_lib.h> 22 #include <jansson.h> 23 #include "taler/taler_mhd_lib.h" 24 #include "taler/taler_json_lib.h" 25 #include "taler/taler_dbevents.h" 26 #include "taler-exchange-httpd_common_deposit.h" 27 #include "taler-exchange-httpd_get-keys.h" 28 #include "taler-exchange-httpd_post-reserves-RESERVE_PUB-open.h" 29 #include "taler-exchange-httpd_responses.h" 30 #include "exchange-database/rollback.h" 31 #include "exchange-database/do_reserve_open.h" 32 #include "exchange-database/insert_reserve_open_deposit.h" 33 34 35 /** 36 * How far do we allow a client's time to be off when 37 * checking the request timestamp? 38 */ 39 #define TIMESTAMP_TOLERANCE \ 40 GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 15) 41 42 43 /** 44 * Closure for #reserve_open_transaction. 45 */ 46 struct ReserveOpenContext 47 { 48 /** 49 * Public key of the reserve the inquiry is about. 50 */ 51 const struct TALER_ReservePublicKeyP *reserve_pub; 52 53 /** 54 * Desired (minimum) expiration time for the reserve. 55 */ 56 struct GNUNET_TIME_Timestamp desired_expiration; 57 58 /** 59 * Actual expiration time for the reserve. 60 */ 61 struct GNUNET_TIME_Timestamp reserve_expiration; 62 63 /** 64 * Timestamp of the request. 65 */ 66 struct GNUNET_TIME_Timestamp timestamp; 67 68 /** 69 * Client signature approving the request. 70 */ 71 struct TALER_ReserveSignatureP reserve_sig; 72 73 /** 74 * Global fees applying to the request. 75 */ 76 const struct TEH_GlobalFee *gf; 77 78 /** 79 * Amount to be paid from the reserve. 80 */ 81 struct TALER_Amount reserve_payment; 82 83 /** 84 * Actual cost to open the reserve. 85 */ 86 struct TALER_Amount open_cost; 87 88 /** 89 * Total amount that was deposited. 90 */ 91 struct TALER_Amount total; 92 93 /** 94 * Information about payments by coin. 95 */ 96 struct TEH_PurseDepositedCoin *payments; 97 98 /** 99 * Length of the @e payments array. 100 */ 101 unsigned int payments_len; 102 103 /** 104 * Desired minimum purse limit. 105 */ 106 uint32_t purse_limit; 107 108 /** 109 * Set to true if the reserve balance is too low 110 * for the operation. 111 */ 112 bool no_funds; 113 114 }; 115 116 117 /** 118 * Send reserve open to client. 119 * 120 * @param connection connection to the client 121 * @param rsc reserve open data to return 122 * @return MHD result code 123 */ 124 static enum MHD_Result 125 reply_reserve_open_success (struct MHD_Connection *connection, 126 const struct ReserveOpenContext *rsc) 127 { 128 struct GNUNET_TIME_Timestamp now; 129 struct GNUNET_TIME_Timestamp re; 130 unsigned int status; 131 132 status = MHD_HTTP_OK; 133 if (GNUNET_TIME_timestamp_cmp (rsc->reserve_expiration, 134 <, 135 rsc->desired_expiration)) 136 status = MHD_HTTP_PAYMENT_REQUIRED; 137 now = GNUNET_TIME_timestamp_get (); 138 if (GNUNET_TIME_timestamp_cmp (rsc->reserve_expiration, 139 <, 140 now)) 141 re = now; 142 else 143 re = rsc->reserve_expiration; 144 return TALER_MHD_REPLY_JSON_PACK ( 145 connection, 146 status, 147 GNUNET_JSON_pack_timestamp ("reserve_expiration", 148 re), 149 TALER_JSON_pack_amount ("open_cost", 150 &rsc->open_cost)); 151 } 152 153 154 /** 155 * Cleans up information in @a rsc, but does not 156 * free @a rsc itself (allocated on the stack!). 157 * 158 * @param[in] rsc struct with information to clean up 159 */ 160 static void 161 cleanup_rsc (struct ReserveOpenContext *rsc) 162 { 163 for (unsigned int i = 0; i<rsc->payments_len; i++) 164 { 165 TEH_common_purse_deposit_free_coin (&rsc->payments[i]); 166 } 167 GNUNET_free (rsc->payments); 168 } 169 170 171 /** 172 * Function implementing /reserves/$RID/open transaction. Given the public 173 * key of a reserve, return the associated transaction open. Runs the 174 * transaction logic; IF it returns a non-error code, the transaction logic 175 * MUST NOT queue a MHD response. IF it returns an hard error, the 176 * transaction logic MUST queue a MHD response and set @a mhd_ret. IF it 177 * returns the soft error code, the function MAY be called again to retry and 178 * MUST not queue a MHD response. 179 * 180 * @param cls a `struct ReserveOpenContext *` 181 * @param connection MHD request which triggered the transaction 182 * @param[out] mhd_ret set to MHD response status for @a connection, 183 * if transaction failed (!) 184 * @return transaction status 185 */ 186 static enum GNUNET_DB_QueryStatus 187 reserve_open_transaction (void *cls, 188 struct MHD_Connection *connection, 189 enum MHD_Result *mhd_ret) 190 { 191 struct ReserveOpenContext *rsc = cls; 192 enum GNUNET_DB_QueryStatus qs; 193 struct TALER_Amount reserve_balance; 194 195 for (unsigned int i = 0; i<rsc->payments_len; i++) 196 { 197 struct TEH_PurseDepositedCoin *coin = &rsc->payments[i]; 198 bool insufficient_funds = true; 199 200 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 201 "Make coin %u known\n", 202 i); 203 qs = TEH_make_coin_known (&coin->cpi, 204 connection, 205 &coin->known_coin_id, 206 mhd_ret); 207 if (qs < 0) 208 return qs; 209 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 210 "Insert open deposit %u known\n", 211 i); 212 qs = TALER_EXCHANGEDB_insert_reserve_open_deposit ( 213 TEH_pg, 214 &coin->cpi, 215 &coin->coin_sig, 216 coin->known_coin_id, 217 &coin->amount, 218 &rsc->reserve_sig, 219 rsc->reserve_pub, 220 &insufficient_funds); 221 /* 0 == qs is fine, then the coin was already 222 spent for this very operation as identified 223 by reserve_sig! */ 224 if (qs < 0) 225 { 226 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 227 return qs; 228 GNUNET_break (0); 229 *mhd_ret = TALER_MHD_reply_with_error (connection, 230 MHD_HTTP_INTERNAL_SERVER_ERROR, 231 TALER_EC_GENERIC_DB_STORE_FAILED, 232 "insert_reserve_open_deposit"); 233 return qs; 234 } 235 if (insufficient_funds) 236 { 237 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 238 "Handle insufficient funds\n"); 239 *mhd_ret 240 = TEH_RESPONSE_reply_coin_insufficient_funds ( 241 connection, 242 TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS, 243 &coin->cpi.denom_pub_hash, 244 &coin->cpi.coin_pub); 245 return GNUNET_DB_STATUS_HARD_ERROR; 246 } 247 } 248 249 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 250 "Do reserve open with reserve payment of %s\n", 251 TALER_amount2s (&rsc->total)); 252 qs = TALER_EXCHANGEDB_do_reserve_open (TEH_pg, 253 /* inputs */ 254 rsc->reserve_pub, 255 &rsc->total, 256 &rsc->reserve_payment, 257 rsc->purse_limit, 258 &rsc->reserve_sig, 259 rsc->desired_expiration, 260 rsc->timestamp, 261 &rsc->gf->fees.account, 262 /* outputs */ 263 &rsc->no_funds, 264 &reserve_balance, 265 &rsc->open_cost, 266 &rsc->reserve_expiration); 267 switch (qs) 268 { 269 case GNUNET_DB_STATUS_HARD_ERROR: 270 GNUNET_break (0); 271 *mhd_ret 272 = TALER_MHD_reply_with_error (connection, 273 MHD_HTTP_INTERNAL_SERVER_ERROR, 274 TALER_EC_GENERIC_DB_FETCH_FAILED, 275 "do_reserve_open"); 276 return GNUNET_DB_STATUS_HARD_ERROR; 277 case GNUNET_DB_STATUS_SOFT_ERROR: 278 return qs; 279 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 280 *mhd_ret 281 = TALER_MHD_reply_with_error (connection, 282 MHD_HTTP_NOT_FOUND, 283 TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN, 284 NULL); 285 return GNUNET_DB_STATUS_HARD_ERROR; 286 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 287 break; 288 } 289 if (rsc->no_funds) 290 { 291 TALER_EXCHANGEDB_rollback (TEH_pg); 292 *mhd_ret 293 = TEH_RESPONSE_reply_reserve_insufficient_balance ( 294 connection, 295 TALER_EC_EXCHANGE_RESERVES_OPEN_INSUFFICIENT_FUNDS, 296 &reserve_balance, 297 &rsc->reserve_payment, 298 rsc->reserve_pub); 299 return GNUNET_DB_STATUS_HARD_ERROR; 300 } 301 return qs; 302 } 303 304 305 enum MHD_Result 306 TEH_handler_reserves_open (struct TEH_RequestContext *rc, 307 const struct TALER_ReservePublicKeyP *reserve_pub, 308 const json_t *root) 309 { 310 struct ReserveOpenContext rsc; 311 const json_t *payments; 312 struct GNUNET_JSON_Specification spec[] = { 313 GNUNET_JSON_spec_timestamp ("request_timestamp", 314 &rsc.timestamp), 315 GNUNET_JSON_spec_timestamp ("reserve_expiration", 316 &rsc.desired_expiration), 317 GNUNET_JSON_spec_fixed_auto ("reserve_sig", 318 &rsc.reserve_sig), 319 GNUNET_JSON_spec_uint32 ("purse_limit", 320 &rsc.purse_limit), 321 GNUNET_JSON_spec_array_const ("payments", 322 &payments), 323 TALER_JSON_spec_amount ("reserve_payment", 324 TEH_currency, 325 &rsc.reserve_payment), 326 GNUNET_JSON_spec_end () 327 }; 328 329 rsc.reserve_pub = reserve_pub; 330 { 331 enum GNUNET_GenericReturnValue res; 332 333 res = TALER_MHD_parse_json_data (rc->connection, 334 root, 335 spec); 336 if (GNUNET_SYSERR == res) 337 { 338 GNUNET_break (0); 339 return MHD_NO; /* hard failure */ 340 } 341 if (GNUNET_NO == res) 342 { 343 GNUNET_break_op (0); 344 return MHD_YES; /* failure */ 345 } 346 } 347 348 { 349 struct GNUNET_TIME_Timestamp now; 350 351 now = GNUNET_TIME_timestamp_get (); 352 if (! GNUNET_TIME_absolute_approx_eq (now.abs_time, 353 rsc.timestamp.abs_time, 354 TIMESTAMP_TOLERANCE)) 355 { 356 GNUNET_break_op (0); 357 return TALER_MHD_reply_with_error (rc->connection, 358 MHD_HTTP_BAD_REQUEST, 359 TALER_EC_EXCHANGE_GENERIC_CLOCK_SKEW, 360 NULL); 361 } 362 } 363 364 if (json_array_size (payments) > TALER_MAX_COINS) 365 { 366 GNUNET_break_op (0); 367 return TALER_MHD_reply_with_error (rc->connection, 368 MHD_HTTP_BAD_REQUEST, 369 TALER_EC_GENERIC_PARAMETER_MALFORMED, 370 "payments"); 371 } 372 rsc.payments_len = json_array_size (payments); 373 rsc.payments = GNUNET_new_array (rsc.payments_len, 374 struct TEH_PurseDepositedCoin); 375 rsc.total = rsc.reserve_payment; 376 for (unsigned int i = 0; i<rsc.payments_len; i++) 377 { 378 struct TEH_PurseDepositedCoin *coin = &rsc.payments[i]; 379 enum GNUNET_GenericReturnValue res; 380 381 res = TEH_common_purse_deposit_parse_coin ( 382 rc->connection, 383 coin, 384 json_array_get (payments, 385 i)); 386 if (GNUNET_SYSERR == res) 387 { 388 GNUNET_break (0); 389 cleanup_rsc (&rsc); 390 return MHD_NO; /* hard failure */ 391 } 392 if (GNUNET_NO == res) 393 { 394 GNUNET_break_op (0); 395 cleanup_rsc (&rsc); 396 return MHD_YES; /* failure */ 397 } 398 if (GNUNET_OK != 399 TALER_wallet_reserve_open_deposit_verify ( 400 &coin->amount, 401 &coin->cpi.denom_pub_hash, 402 coin->cpi.no_age_commitment 403 ? NULL 404 : &coin->cpi.h_age_commitment, 405 &rsc.reserve_sig, 406 &coin->cpi.coin_pub, 407 &coin->coin_sig)) 408 { 409 GNUNET_break_op (0); 410 cleanup_rsc (&rsc); 411 return TALER_MHD_reply_with_error ( 412 rc->connection, 413 MHD_HTTP_FORBIDDEN, 414 TALER_EC_EXCHANGE_RESERVES_OPEN_COIN_SIGNATURE_INVALID, 415 "TALER_wallet_reserve_open_deposit_verify failed"); 416 } 417 if (0 > 418 TALER_amount_add (&rsc.total, 419 &rsc.total, 420 &coin->amount_minus_fee)) 421 { 422 GNUNET_break (0); 423 cleanup_rsc (&rsc); 424 return TALER_MHD_reply_with_error ( 425 rc->connection, 426 MHD_HTTP_INTERNAL_SERVER_ERROR, 427 TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT, 428 NULL); 429 } 430 } 431 432 { 433 struct TEH_KeyStateHandle *keys; 434 435 keys = TEH_keys_get_state (); 436 if (NULL == keys) 437 { 438 GNUNET_break (0); 439 cleanup_rsc (&rsc); 440 return TALER_MHD_reply_with_error (rc->connection, 441 MHD_HTTP_INTERNAL_SERVER_ERROR, 442 TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, 443 NULL); 444 } 445 rsc.gf = TEH_keys_global_fee_by_time (keys, 446 rsc.timestamp); 447 } 448 if (NULL == rsc.gf) 449 { 450 GNUNET_break (0); 451 cleanup_rsc (&rsc); 452 return TALER_MHD_reply_with_error (rc->connection, 453 MHD_HTTP_INTERNAL_SERVER_ERROR, 454 TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION, 455 NULL); 456 } 457 458 if (GNUNET_OK != 459 TALER_wallet_reserve_open_verify (&rsc.reserve_payment, 460 rsc.timestamp, 461 rsc.desired_expiration, 462 rsc.purse_limit, 463 reserve_pub, 464 &rsc.reserve_sig)) 465 { 466 GNUNET_break_op (0); 467 cleanup_rsc (&rsc); 468 return TALER_MHD_reply_with_error (rc->connection, 469 MHD_HTTP_FORBIDDEN, 470 TALER_EC_EXCHANGE_RESERVES_OPEN_BAD_SIGNATURE, 471 NULL); 472 } 473 474 { 475 enum MHD_Result mhd_ret; 476 477 if (GNUNET_OK != 478 TEH_DB_run_transaction (rc->connection, 479 "reserve open", 480 TEH_MT_REQUEST_OTHER, 481 &mhd_ret, 482 &reserve_open_transaction, 483 &rsc)) 484 { 485 cleanup_rsc (&rsc); 486 return mhd_ret; 487 } 488 } 489 490 { 491 enum MHD_Result mhd_ret; 492 493 mhd_ret = reply_reserve_open_success (rc->connection, 494 &rsc); 495 cleanup_rsc (&rsc); 496 return mhd_ret; 497 } 498 } 499 500 501 /* end of taler-exchange-httpd_reserves_open.c */