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