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