taler-exchange-httpd_get-deposits.c (15333B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2014-2024 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Affero General Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 12 13 You should have received a copy of the GNU Affero General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file taler-exchange-httpd_get-deposits.c 18 * @brief Handle wire deposit tracking-related requests 19 * @author Christian Grothoff 20 */ 21 #include "taler/platform.h" 22 #include <gnunet/gnunet_util_lib.h> 23 #include <jansson.h> 24 #include <microhttpd.h> 25 #include <pthread.h> 26 #include "taler/taler_dbevents.h" 27 #include "taler/taler_json_lib.h" 28 #include "taler/taler_util.h" 29 #include "taler/taler_mhd_lib.h" 30 #include "taler/taler_signatures.h" 31 #include "taler-exchange-httpd_get-keys.h" 32 #include "taler-exchange-httpd_get-deposits.h" 33 #include "taler-exchange-httpd_responses.h" 34 35 36 /** 37 * Closure for #handle_wtid_data. 38 */ 39 struct DepositWtidContext 40 { 41 42 /** 43 * Kept in a DLL. 44 */ 45 struct DepositWtidContext *next; 46 47 /** 48 * Kept in a DLL. 49 */ 50 struct DepositWtidContext *prev; 51 52 /** 53 * Context for the request we are processing. 54 */ 55 struct TEH_RequestContext *rc; 56 57 /** 58 * Subscription for the database event we are waiting for. 59 */ 60 struct GNUNET_DB_EventHandler *eh; 61 62 /** 63 * Hash over the proposal data of the contract for which this deposit is made. 64 */ 65 struct TALER_PrivateContractHashP h_contract_terms; 66 67 /** 68 * Hash over the wiring information of the merchant. 69 */ 70 struct TALER_MerchantWireHashP h_wire; 71 72 /** 73 * The Merchant's public key. The deposit inquiry request is to be 74 * signed by the corresponding private key (using EdDSA). 75 */ 76 struct TALER_MerchantPublicKeyP merchant; 77 78 /** 79 * Public key for KYC operations on the target bank 80 * account for the wire transfer. All zero if no 81 * public key is accepted yet. In that case, the 82 * client should use the @e merchant public key for 83 * the KYC auth wire transfer. 84 */ 85 union TALER_AccountPublicKeyP account_pub; 86 87 /** 88 * The coin's public key. This is the value that must have been 89 * signed (blindly) by the Exchange. 90 */ 91 struct TALER_CoinSpendPublicKeyP coin_pub; 92 93 /** 94 * Set by #handle_wtid data to the wire transfer ID. 95 */ 96 struct TALER_WireTransferIdentifierRawP wtid; 97 98 /** 99 * Signature by the merchant. 100 */ 101 struct TALER_MerchantSignatureP merchant_sig; 102 103 /** 104 * Set by #handle_wtid data to the coin's contribution to the wire transfer. 105 */ 106 struct TALER_Amount coin_contribution; 107 108 /** 109 * Set by #handle_wtid data to the fee charged to the coin. 110 */ 111 struct TALER_Amount coin_fee; 112 113 /** 114 * Set by #handle_wtid data to the wire transfer execution time. 115 */ 116 struct GNUNET_TIME_Timestamp execution_time; 117 118 /** 119 * Timeout of the request, for long-polling. 120 */ 121 struct GNUNET_TIME_Absolute timeout; 122 123 /** 124 * Set by #handle_wtid to the coin contribution to the transaction 125 * (that is, @e coin_contribution minus @e coin_fee). 126 */ 127 struct TALER_Amount coin_delta; 128 129 /** 130 * KYC status information for the receiving account. 131 */ 132 struct TALER_EXCHANGEDB_KycStatus kyc; 133 134 /** 135 * #GNUNET_YES if we were suspended, #GNUNET_SYSERR 136 * if we were woken up due to shutdown. 137 */ 138 enum GNUNET_GenericReturnValue suspended; 139 140 /** 141 * What do we long-poll for? Defaults to 142 * #TALER_DGLPT_OK if not given. 143 */ 144 enum TALER_DepositGetLongPollTarget lpt; 145 }; 146 147 148 /** 149 * Head of DLL of suspended requests. 150 */ 151 static struct DepositWtidContext *dwc_head; 152 153 /** 154 * Tail of DLL of suspended requests. 155 */ 156 static struct DepositWtidContext *dwc_tail; 157 158 159 void 160 TEH_deposits_get_cleanup () 161 { 162 struct DepositWtidContext *n; 163 164 for (struct DepositWtidContext *ctx = dwc_head; 165 NULL != ctx; 166 ctx = n) 167 { 168 n = ctx->next; 169 GNUNET_assert (GNUNET_YES == ctx->suspended); 170 ctx->suspended = GNUNET_SYSERR; 171 MHD_resume_connection (ctx->rc->connection); 172 GNUNET_CONTAINER_DLL_remove (dwc_head, 173 dwc_tail, 174 ctx); 175 } 176 } 177 178 179 /** 180 * A merchant asked for details about a deposit. Provide 181 * them. Generates the 200 reply. 182 * 183 * @param ctx details to respond with 184 * @return MHD result code 185 */ 186 static MHD_RESULT 187 reply_deposit_details ( 188 const struct DepositWtidContext *ctx) 189 { 190 struct MHD_Connection *connection = ctx->rc->connection; 191 struct TALER_ExchangePublicKeyP pub; 192 struct TALER_ExchangeSignatureP sig; 193 enum TALER_ErrorCode ec; 194 195 if (TALER_EC_NONE != 196 (ec = TALER_exchange_online_confirm_wire_sign ( 197 &TEH_keys_exchange_sign_, 198 &ctx->h_wire, 199 &ctx->h_contract_terms, 200 &ctx->wtid, 201 &ctx->coin_pub, 202 ctx->execution_time, 203 &ctx->coin_delta, 204 &pub, 205 &sig))) 206 { 207 GNUNET_break (0); 208 return TALER_MHD_reply_with_ec (connection, 209 ec, 210 NULL); 211 } 212 return TALER_MHD_REPLY_JSON_PACK ( 213 connection, 214 MHD_HTTP_OK, 215 GNUNET_JSON_pack_data_auto ("wtid", 216 &ctx->wtid), 217 GNUNET_JSON_pack_timestamp ("execution_time", 218 ctx->execution_time), 219 TALER_JSON_pack_amount ("coin_contribution", 220 &ctx->coin_delta), 221 GNUNET_JSON_pack_data_auto ("exchange_sig", 222 &sig), 223 GNUNET_JSON_pack_data_auto ("exchange_pub", 224 &pub)); 225 } 226 227 228 /** 229 * Function called on events received from Postgres. 230 * Wakes up long pollers. 231 * 232 * @param cls the `struct DepositWtidContext *` 233 * @param extra additional event data provided 234 * @param extra_size number of bytes in @a extra 235 */ 236 static void 237 db_event_cb (void *cls, 238 const void *extra, 239 size_t extra_size) 240 { 241 struct DepositWtidContext *ctx = cls; 242 struct GNUNET_AsyncScopeSave old_scope; 243 244 (void) extra; 245 (void) extra_size; 246 if (GNUNET_YES != ctx->suspended) 247 return; /* might get multiple wake-up events */ 248 GNUNET_CONTAINER_DLL_remove (dwc_head, 249 dwc_tail, 250 ctx); 251 GNUNET_async_scope_enter (&ctx->rc->async_scope_id, 252 &old_scope); 253 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 254 "Resuming request handling\n"); 255 TEH_check_invariants (); 256 ctx->suspended = GNUNET_NO; 257 MHD_resume_connection (ctx->rc->connection); 258 TALER_MHD_daemon_trigger (); 259 TEH_check_invariants (); 260 GNUNET_async_scope_restore (&old_scope); 261 } 262 263 264 /** 265 * Lookup and return the wire transfer identifier. 266 * 267 * @param ctx context of the signed request to execute 268 * @return MHD result code 269 */ 270 static MHD_RESULT 271 handle_track_transaction_request ( 272 struct DepositWtidContext *ctx) 273 { 274 struct MHD_Connection *connection = ctx->rc->connection; 275 enum GNUNET_DB_QueryStatus qs; 276 bool pending; 277 struct TALER_Amount fee; 278 279 qs = TEH_plugin->lookup_transfer_by_deposit ( 280 TEH_plugin->cls, 281 &ctx->h_contract_terms, 282 &ctx->h_wire, 283 &ctx->coin_pub, 284 &ctx->merchant, 285 &pending, 286 &ctx->wtid, 287 &ctx->execution_time, 288 &ctx->coin_contribution, 289 &fee, 290 &ctx->kyc, 291 &ctx->account_pub); 292 if (0 > qs) 293 { 294 GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); 295 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 296 return TALER_MHD_reply_with_error ( 297 connection, 298 MHD_HTTP_INTERNAL_SERVER_ERROR, 299 TALER_EC_GENERIC_DB_FETCH_FAILED, 300 "lookup_transfer_by_deposit"); 301 } 302 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 303 { 304 return TALER_MHD_reply_with_error ( 305 connection, 306 MHD_HTTP_NOT_FOUND, 307 TALER_EC_EXCHANGE_DEPOSITS_GET_NOT_FOUND, 308 NULL); 309 } 310 311 if (0 > 312 TALER_amount_subtract (&ctx->coin_delta, 313 &ctx->coin_contribution, 314 &fee)) 315 { 316 GNUNET_break (0); 317 return TALER_MHD_reply_with_error ( 318 connection, 319 MHD_HTTP_INTERNAL_SERVER_ERROR, 320 TALER_EC_GENERIC_DB_INVARIANT_FAILURE, 321 "wire fees exceed aggregate in database"); 322 } 323 if (pending) 324 { 325 if (GNUNET_TIME_absolute_is_future (ctx->timeout)) 326 { 327 bool do_suspend = false; 328 switch (ctx->lpt) 329 { 330 case TALER_DGLPT_NONE: 331 break; 332 case TALER_DGLPT_KYC_REQUIRED_OR_OK: 333 do_suspend = ctx->kyc.ok; 334 break; 335 case TALER_DGLPT_OK: 336 do_suspend = true; 337 break; 338 } 339 if (do_suspend) 340 { 341 GNUNET_assert (GNUNET_NO == ctx->suspended); 342 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 343 "Suspending request handling\n"); 344 GNUNET_CONTAINER_DLL_insert (dwc_head, 345 dwc_tail, 346 ctx); 347 ctx->suspended = GNUNET_YES; 348 MHD_suspend_connection (connection); 349 return MHD_YES; 350 } 351 } 352 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 353 "KYC required with row %llu\n", 354 (unsigned long long) ctx->kyc.requirement_row); 355 return TALER_MHD_REPLY_JSON_PACK ( 356 connection, 357 MHD_HTTP_ACCEPTED, 358 GNUNET_JSON_pack_allow_null ( 359 (0 == ctx->kyc.requirement_row) 360 ? GNUNET_JSON_pack_string ("requirement_row", 361 NULL) 362 : GNUNET_JSON_pack_uint64 ("requirement_row", 363 ctx->kyc.requirement_row)), 364 GNUNET_JSON_pack_allow_null ( 365 (GNUNET_is_zero (&ctx->account_pub)) 366 ? GNUNET_JSON_pack_string ("account_pub", 367 NULL) 368 : GNUNET_JSON_pack_data_auto ("account_pub", 369 &ctx->account_pub)), 370 GNUNET_JSON_pack_bool ("kyc_ok", 371 ctx->kyc.ok), 372 GNUNET_JSON_pack_timestamp ("execution_time", 373 ctx->execution_time)); 374 } 375 return reply_deposit_details (ctx); 376 } 377 378 379 /** 380 * Function called to clean up a context. 381 * 382 * @param rc request context with data to clean up 383 */ 384 static void 385 dwc_cleaner (struct TEH_RequestContext *rc) 386 { 387 struct DepositWtidContext *ctx = rc->rh_ctx; 388 389 GNUNET_assert (GNUNET_YES != ctx->suspended); 390 if (NULL != ctx->eh) 391 { 392 TEH_plugin->event_listen_cancel (TEH_plugin->cls, 393 ctx->eh); 394 ctx->eh = NULL; 395 } 396 GNUNET_free (ctx); 397 } 398 399 400 MHD_RESULT 401 TEH_handler_deposits_get (struct TEH_RequestContext *rc, 402 const char *const args[4]) 403 { 404 struct DepositWtidContext *ctx = rc->rh_ctx; 405 406 if (NULL == ctx) 407 { 408 ctx = GNUNET_new (struct DepositWtidContext); 409 ctx->rc = rc; 410 ctx->lpt = TALER_DGLPT_OK; /* default */ 411 rc->rh_ctx = ctx; 412 rc->rh_cleaner = &dwc_cleaner; 413 414 if (GNUNET_OK != 415 GNUNET_STRINGS_string_to_data (args[0], 416 strlen (args[0]), 417 &ctx->h_wire, 418 sizeof (ctx->h_wire))) 419 { 420 GNUNET_break_op (0); 421 return TALER_MHD_reply_with_error ( 422 rc->connection, 423 MHD_HTTP_BAD_REQUEST, 424 TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_H_WIRE, 425 args[0]); 426 } 427 if (GNUNET_OK != 428 GNUNET_STRINGS_string_to_data (args[1], 429 strlen (args[1]), 430 &ctx->merchant, 431 sizeof (ctx->merchant))) 432 { 433 GNUNET_break_op (0); 434 return TALER_MHD_reply_with_error ( 435 rc->connection, 436 MHD_HTTP_BAD_REQUEST, 437 TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_MERCHANT_PUB, 438 args[1]); 439 } 440 if (GNUNET_OK != 441 GNUNET_STRINGS_string_to_data (args[2], 442 strlen (args[2]), 443 &ctx->h_contract_terms, 444 sizeof (ctx->h_contract_terms))) 445 { 446 GNUNET_break_op (0); 447 return TALER_MHD_reply_with_error ( 448 rc->connection, 449 MHD_HTTP_BAD_REQUEST, 450 TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_H_CONTRACT_TERMS, 451 args[2]); 452 } 453 if (GNUNET_OK != 454 GNUNET_STRINGS_string_to_data (args[3], 455 strlen (args[3]), 456 &ctx->coin_pub, 457 sizeof (ctx->coin_pub))) 458 { 459 GNUNET_break_op (0); 460 return TALER_MHD_reply_with_error ( 461 rc->connection, 462 MHD_HTTP_BAD_REQUEST, 463 TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_COIN_PUB, 464 args[3]); 465 } 466 TALER_MHD_parse_request_arg_auto_t (rc->connection, 467 "merchant_sig", 468 &ctx->merchant_sig); 469 TALER_MHD_parse_request_timeout (rc->connection, 470 &ctx->timeout); 471 { 472 uint64_t num = 0; 473 int val; 474 475 TALER_MHD_parse_request_number (rc->connection, 476 "lpt", 477 &num); 478 val = (int) num; 479 if ( (val < 0) || 480 (val > TALER_DGLPT_MAX) ) 481 { 482 /* Protocol violation, but we can be graceful and 483 just ignore the long polling! */ 484 GNUNET_break_op (0); 485 val = TALER_DGLPT_NONE; 486 } 487 ctx->lpt = (enum TALER_DepositGetLongPollTarget) val; 488 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 489 "Long polling for target %d with timeout %s\n", 490 val, 491 GNUNET_TIME_relative2s ( 492 GNUNET_TIME_absolute_get_remaining ( 493 ctx->timeout), 494 true)); 495 } 496 TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; 497 { 498 if (GNUNET_OK != 499 TALER_merchant_deposit_verify (&ctx->merchant, 500 &ctx->coin_pub, 501 &ctx->h_contract_terms, 502 &ctx->h_wire, 503 &ctx->merchant_sig)) 504 { 505 GNUNET_break_op (0); 506 return TALER_MHD_reply_with_error ( 507 rc->connection, 508 MHD_HTTP_FORBIDDEN, 509 TALER_EC_EXCHANGE_DEPOSITS_GET_MERCHANT_SIGNATURE_INVALID, 510 NULL); 511 } 512 } 513 if ( (GNUNET_TIME_absolute_is_future (ctx->timeout)) && 514 (TALER_DGLPT_NONE != ctx->lpt) ) 515 { 516 struct TALER_CoinDepositEventP rep = { 517 .header.size = htons (sizeof (rep)), 518 .header.type = htons (TALER_DBEVENT_EXCHANGE_DEPOSIT_STATUS_CHANGED), 519 .merchant_pub = ctx->merchant 520 }; 521 522 ctx->eh = TEH_plugin->event_listen ( 523 TEH_plugin->cls, 524 GNUNET_TIME_absolute_get_remaining (ctx->timeout), 525 &rep.header, 526 &db_event_cb, 527 ctx); 528 GNUNET_break (NULL != ctx->eh); 529 } 530 } 531 532 return handle_track_transaction_request (ctx); 533 } 534 535 536 /* end of taler-exchange-httpd_deposits_get.c */