fakebank_twg_history.c (18303B)
1 /* 2 This file is part of TALER 3 (C) 2016-2023 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or 6 modify it under the terms of the GNU General Public License 7 as published by the Free Software Foundation; either version 3, 8 or (at your option) any later version. 9 10 TALER is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public 16 License along with TALER; see the file COPYING. If not, 17 see <http://www.gnu.org/licenses/> 18 */ 19 /** 20 * @file bank-lib/fakebank_twg_history.c 21 * @brief routines to return account histories for the Taler Wire Gateway API 22 * @author Christian Grothoff <christian@grothoff.org> 23 */ 24 #include <pthread.h> 25 #include "taler/taler_fakebank_lib.h" 26 #include "taler/taler_bank_service.h" 27 #include "taler/taler_mhd_lib.h" 28 #include <gnunet/gnunet_mhd_compat.h> 29 #include "fakebank.h" 30 #include "fakebank_twg_history.h" 31 #include "fakebank_common_lookup.h" 32 #include "fakebank_common_lp.h" 33 #include "fakebank_common_parser.h" 34 35 /** 36 * Function called to clean up a history context. 37 * 38 * @param cls a `struct HistoryContext *` 39 */ 40 static void 41 history_cleanup (void *cls) 42 { 43 struct HistoryContext *hc = cls; 44 45 json_decref (hc->history); 46 GNUNET_free (hc); 47 } 48 49 50 enum MHD_Result 51 TALER_FAKEBANK_twg_get_debit_history_ ( 52 struct TALER_FAKEBANK_Handle *h, 53 struct MHD_Connection *connection, 54 const char *account, 55 void **con_cls) 56 { 57 struct ConnectionContext *cc = *con_cls; 58 struct HistoryContext *hc; 59 struct Transaction *pos; 60 enum GNUNET_GenericReturnValue ret; 61 bool in_shutdown; 62 const char *acc_payto_uri; 63 64 if (NULL == cc) 65 { 66 cc = GNUNET_new (struct ConnectionContext); 67 cc->ctx_cleaner = &history_cleanup; 68 *con_cls = cc; 69 hc = GNUNET_new (struct HistoryContext); 70 cc->ctx = hc; 71 72 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 73 "Handling /history/outgoing connection %p\n", 74 connection); 75 if (GNUNET_OK != 76 (ret = TALER_FAKEBANK_common_parse_history_args (h, 77 connection, 78 &hc->ha))) 79 { 80 GNUNET_break_op (0); 81 return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES; 82 } 83 GNUNET_assert (0 == 84 pthread_mutex_lock (&h->big_lock)); 85 if (UINT64_MAX == hc->ha.start_idx) 86 hc->ha.start_idx = h->serial_counter; 87 hc->acc = TALER_FAKEBANK_lookup_account_ (h, 88 account, 89 NULL); 90 if (NULL == hc->acc) 91 { 92 GNUNET_assert (0 == 93 pthread_mutex_unlock (&h->big_lock)); 94 return TALER_MHD_reply_with_error (connection, 95 MHD_HTTP_NOT_FOUND, 96 TALER_EC_BANK_UNKNOWN_ACCOUNT, 97 account); 98 } 99 hc->history = json_array (); 100 if (NULL == hc->history) 101 { 102 GNUNET_break (0); 103 GNUNET_assert (0 == 104 pthread_mutex_unlock (&h->big_lock)); 105 return MHD_NO; 106 } 107 hc->timeout = GNUNET_TIME_relative_to_absolute (hc->ha.lp_timeout); 108 } 109 else 110 { 111 hc = cc->ctx; 112 GNUNET_assert (0 == 113 pthread_mutex_lock (&h->big_lock)); 114 } 115 116 if (! hc->ha.have_start) 117 { 118 pos = (0 > hc->ha.delta) 119 ? hc->acc->out_tail 120 : hc->acc->out_head; 121 } 122 else 123 { 124 struct Transaction *t = h->transactions[hc->ha.start_idx % h->ram_limit]; 125 bool overflow; 126 uint64_t dir; 127 bool skip = true; 128 129 dir = (0 > hc->ha.delta) ? (h->ram_limit - 1) : 1; 130 overflow = (NULL != t) && (t->row_id != hc->ha.start_idx); 131 /* If account does not match, linear scan for 132 first matching account. */ 133 while ( (! overflow) && 134 (NULL != t) && 135 (t->debit_account != hc->acc) ) 136 { 137 skip = false; 138 t = h->transactions[(t->row_id + dir) % h->ram_limit]; 139 if ( (NULL != t) && 140 (t->row_id == hc->ha.start_idx) ) 141 overflow = true; /* full circle, give up! */ 142 } 143 if ( (NULL == t) || 144 overflow) 145 { 146 /* FIXME: these conditions are unclear to me. */ 147 if ( (GNUNET_TIME_relative_is_zero (hc->ha.lp_timeout)) && 148 (0 < hc->ha.delta)) 149 { 150 acc_payto_uri = hc->acc->payto_uri; 151 in_shutdown = h->in_shutdown; 152 GNUNET_assert (0 == 153 pthread_mutex_unlock (&h->big_lock)); 154 if (overflow) 155 { 156 return TALER_MHD_reply_with_ec ( 157 connection, 158 TALER_EC_BANK_ANCIENT_TRANSACTION_GONE, 159 NULL); 160 } 161 goto finish; 162 } 163 if (h->in_shutdown) 164 { 165 acc_payto_uri = hc->acc->payto_uri; 166 in_shutdown = h->in_shutdown; 167 GNUNET_assert (0 == 168 pthread_mutex_unlock (&h->big_lock)); 169 goto finish; 170 } 171 TALER_FAKEBANK_start_lp_ (h, 172 connection, 173 hc->acc, 174 GNUNET_TIME_absolute_get_remaining ( 175 hc->timeout), 176 LP_DEBIT, 177 NULL); 178 GNUNET_assert (0 == 179 pthread_mutex_unlock (&h->big_lock)); 180 return MHD_YES; 181 } 182 if (t->debit_account != hc->acc) 183 { 184 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 185 "Invalid start specified, transaction %llu not with account %s!\n", 186 (unsigned long long) hc->ha.start_idx, 187 account); 188 GNUNET_assert (0 == 189 pthread_mutex_unlock (&h->big_lock)); 190 return MHD_NO; 191 } 192 if (skip) 193 { 194 /* range is exclusive, skip the matching entry */ 195 if (0 > hc->ha.delta) 196 pos = t->prev_out; 197 else 198 pos = t->next_out; 199 } 200 else 201 { 202 pos = t; 203 } 204 } 205 if (NULL != pos) 206 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 207 "Returning %lld debit transactions starting (inclusive) from %llu\n", 208 (long long) hc->ha.delta, 209 (unsigned long long) pos->row_id); 210 while ( (0 != hc->ha.delta) && 211 (NULL != pos) ) 212 { 213 json_t *trans; 214 char *credit_payto; 215 216 if (T_DEBIT != pos->type) 217 { 218 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 219 "Unexpected CREDIT transaction #%llu for account `%s'\n", 220 (unsigned long long) pos->row_id, 221 account); 222 if (0 > hc->ha.delta) 223 pos = pos->prev_out; 224 if (0 < hc->ha.delta) 225 pos = pos->next_out; 226 continue; 227 } 228 GNUNET_asprintf (&credit_payto, 229 "payto://x-taler-bank/%s/%s?receiver-name=%s", 230 h->hostname, 231 pos->credit_account->account_name, 232 pos->credit_account->receiver_name); 233 234 trans = GNUNET_JSON_PACK ( 235 GNUNET_JSON_pack_uint64 ("row_id", 236 pos->row_id), 237 GNUNET_JSON_pack_timestamp ("date", 238 pos->date), 239 TALER_JSON_pack_amount ("amount", 240 &pos->amount), 241 GNUNET_JSON_pack_string ("credit_account", 242 credit_payto), 243 GNUNET_JSON_pack_string ("exchange_base_url", 244 pos->subject.debit.exchange_base_url), 245 GNUNET_JSON_pack_data_auto ("wtid", 246 &pos->subject.debit.wtid)); 247 GNUNET_assert (NULL != trans); 248 GNUNET_free (credit_payto); 249 GNUNET_assert (0 == 250 json_array_append_new (hc->history, 251 trans)); 252 if (hc->ha.delta > 0) 253 hc->ha.delta--; 254 else 255 hc->ha.delta++; 256 if (0 > hc->ha.delta) 257 pos = pos->prev_out; 258 if (0 < hc->ha.delta) 259 pos = pos->next_out; 260 } 261 if ( (0 == json_array_size (hc->history)) && 262 (! h->in_shutdown) && 263 (GNUNET_TIME_absolute_is_future (hc->timeout)) && 264 (0 < hc->ha.delta)) 265 { 266 TALER_FAKEBANK_start_lp_ (h, 267 connection, 268 hc->acc, 269 GNUNET_TIME_absolute_get_remaining (hc->timeout), 270 LP_DEBIT, 271 NULL); 272 GNUNET_assert (0 == 273 pthread_mutex_unlock (&h->big_lock)); 274 return MHD_YES; 275 } 276 in_shutdown = h->in_shutdown; 277 acc_payto_uri = hc->acc->payto_uri; 278 GNUNET_assert (0 == 279 pthread_mutex_unlock (&h->big_lock)); 280 finish: 281 if (0 == json_array_size (hc->history)) 282 { 283 GNUNET_break (in_shutdown || 284 (! GNUNET_TIME_absolute_is_future (hc->timeout))); 285 return TALER_MHD_reply_static (connection, 286 MHD_HTTP_NO_CONTENT, 287 NULL, 288 NULL, 289 0); 290 } 291 { 292 json_t *jh = hc->history; 293 294 hc->history = NULL; 295 return TALER_MHD_REPLY_JSON_PACK ( 296 connection, 297 MHD_HTTP_OK, 298 GNUNET_JSON_pack_string ( 299 "debit_account", 300 acc_payto_uri), 301 GNUNET_JSON_pack_array_steal ( 302 "outgoing_transactions", 303 jh)); 304 } 305 } 306 307 308 enum MHD_Result 309 TALER_FAKEBANK_twg_get_credit_history_ ( 310 struct TALER_FAKEBANK_Handle *h, 311 struct MHD_Connection *connection, 312 const char *account, 313 void **con_cls) 314 { 315 struct ConnectionContext *cc = *con_cls; 316 struct HistoryContext *hc; 317 const struct Transaction *pos; 318 enum GNUNET_GenericReturnValue ret; 319 bool in_shutdown; 320 const char *acc_payto_uri; 321 322 if (NULL == cc) 323 { 324 cc = GNUNET_new (struct ConnectionContext); 325 cc->ctx_cleaner = &history_cleanup; 326 *con_cls = cc; 327 hc = GNUNET_new (struct HistoryContext); 328 cc->ctx = hc; 329 hc->history = json_array (); 330 if (NULL == hc->history) 331 { 332 GNUNET_break (0); 333 return MHD_NO; 334 } 335 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 336 "Handling /history/incoming connection %p\n", 337 connection); 338 if (GNUNET_OK != 339 (ret = TALER_FAKEBANK_common_parse_history_args (h, 340 connection, 341 &hc->ha))) 342 { 343 GNUNET_break_op (0); 344 return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES; 345 } 346 hc->timeout = GNUNET_TIME_relative_to_absolute (hc->ha.lp_timeout); 347 GNUNET_assert (0 == 348 pthread_mutex_lock (&h->big_lock)); 349 if (UINT64_MAX == hc->ha.start_idx) 350 hc->ha.start_idx = h->serial_counter; 351 hc->acc = TALER_FAKEBANK_lookup_account_ (h, 352 account, 353 NULL); 354 if (NULL == hc->acc) 355 { 356 GNUNET_assert (0 == 357 pthread_mutex_unlock (&h->big_lock)); 358 return TALER_MHD_reply_with_error (connection, 359 MHD_HTTP_NOT_FOUND, 360 TALER_EC_BANK_UNKNOWN_ACCOUNT, 361 account); 362 } 363 } 364 else 365 { 366 hc = cc->ctx; 367 GNUNET_assert (0 == 368 pthread_mutex_lock (&h->big_lock)); 369 } 370 371 if (! hc->ha.have_start) 372 { 373 pos = (0 > hc->ha.delta) 374 ? hc->acc->in_tail 375 : hc->acc->in_head; 376 } 377 else 378 { 379 struct Transaction *t = h->transactions[hc->ha.start_idx % h->ram_limit]; 380 bool overflow; 381 uint64_t dir; 382 bool skip = true; 383 384 overflow = ( (NULL != t) && (t->row_id != hc->ha.start_idx) ); 385 dir = (0 > hc->ha.delta) ? (h->ram_limit - 1) : 1; 386 /* If account does not match, linear scan for 387 first matching account. */ 388 while ( (! overflow) && 389 (NULL != t) && 390 (t->credit_account != hc->acc) ) 391 { 392 skip = false; 393 t = h->transactions[(t->row_id + dir) % h->ram_limit]; 394 if ( (NULL != t) && 395 (t->row_id == hc->ha.start_idx) ) 396 overflow = true; /* full circle, give up! */ 397 } 398 if ( (NULL == t) || 399 overflow) 400 { 401 in_shutdown = h->in_shutdown; 402 /* FIXME: these conditions are unclear to me. */ 403 if (GNUNET_TIME_relative_is_zero (hc->ha.lp_timeout) && 404 (0 < hc->ha.delta)) 405 { 406 acc_payto_uri = hc->acc->payto_uri; 407 GNUNET_assert (0 == 408 pthread_mutex_unlock (&h->big_lock)); 409 if (overflow) 410 return TALER_MHD_reply_with_ec ( 411 connection, 412 TALER_EC_BANK_ANCIENT_TRANSACTION_GONE, 413 NULL); 414 goto finish; 415 } 416 if (in_shutdown) 417 { 418 acc_payto_uri = hc->acc->payto_uri; 419 GNUNET_assert (0 == 420 pthread_mutex_unlock (&h->big_lock)); 421 goto finish; 422 } 423 TALER_FAKEBANK_start_lp_ (h, 424 connection, 425 hc->acc, 426 GNUNET_TIME_absolute_get_remaining ( 427 hc->timeout), 428 LP_CREDIT, 429 NULL); 430 GNUNET_assert (0 == 431 pthread_mutex_unlock (&h->big_lock)); 432 return MHD_YES; 433 } 434 if (skip) 435 { 436 /* range from application is exclusive, skip the 437 matching entry */ 438 if (0 > hc->ha.delta) 439 pos = t->prev_in; 440 else 441 pos = t->next_in; 442 } 443 else 444 { 445 pos = t; 446 } 447 } 448 if (NULL != pos) 449 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 450 "Returning %lld credit transactions starting (inclusive) from %llu\n", 451 (long long) hc->ha.delta, 452 (unsigned long long) pos->row_id); 453 while ( (0 != hc->ha.delta) && 454 (NULL != pos) ) 455 { 456 json_t *trans; 457 458 if ( (T_CREDIT != pos->type) && 459 (T_AUTH != pos->type) ) 460 { 461 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 462 "Unexpected DEBIT transaction #%llu for account `%s'\n", 463 (unsigned long long) pos->row_id, 464 account); 465 if (0 > hc->ha.delta) 466 pos = pos->prev_in; 467 if (0 < hc->ha.delta) 468 pos = pos->next_in; 469 continue; 470 } 471 switch (pos->type) 472 { 473 case T_DEBIT: 474 GNUNET_assert (0); 475 break; 476 case T_WAD: 477 trans = GNUNET_JSON_PACK ( 478 GNUNET_JSON_pack_string ("type", 479 "WAD"), 480 GNUNET_JSON_pack_uint64 ("row_id", 481 pos->row_id), 482 GNUNET_JSON_pack_timestamp ("date", 483 pos->date), 484 TALER_JSON_pack_amount ("amount", 485 &pos->amount), 486 GNUNET_JSON_pack_string ("debit_account", 487 pos->debit_account->payto_uri), 488 GNUNET_JSON_pack_string ("origin_exchange_url", 489 pos->subject.wad.origin_base_url), 490 GNUNET_JSON_pack_data_auto ("wad_id", 491 &pos->subject.wad.wad_id)); 492 break; 493 case T_CREDIT: 494 trans = GNUNET_JSON_PACK ( 495 GNUNET_JSON_pack_string ("type", 496 "RESERVE"), 497 GNUNET_JSON_pack_uint64 ("row_id", 498 pos->row_id), 499 GNUNET_JSON_pack_timestamp ("date", 500 pos->date), 501 TALER_JSON_pack_amount ("amount", 502 &pos->amount), 503 GNUNET_JSON_pack_string ("debit_account", 504 pos->debit_account->payto_uri), 505 GNUNET_JSON_pack_data_auto ("reserve_pub", 506 &pos->subject.credit.reserve_pub)); 507 break; 508 case T_AUTH: 509 trans = GNUNET_JSON_PACK ( 510 GNUNET_JSON_pack_string ("type", 511 "KYCAUTH"), 512 GNUNET_JSON_pack_uint64 ("row_id", 513 pos->row_id), 514 GNUNET_JSON_pack_timestamp ("date", 515 pos->date), 516 TALER_JSON_pack_amount ("amount", 517 &pos->amount), 518 GNUNET_JSON_pack_string ("debit_account", 519 pos->debit_account->payto_uri), 520 GNUNET_JSON_pack_data_auto ("account_pub", 521 &pos->subject.auth.account_pub)); 522 break; 523 } 524 GNUNET_assert (NULL != trans); 525 GNUNET_assert (0 == 526 json_array_append_new (hc->history, 527 trans)); 528 if (hc->ha.delta > 0) 529 hc->ha.delta--; 530 else 531 hc->ha.delta++; 532 if (0 > hc->ha.delta) 533 pos = pos->prev_in; 534 if (0 < hc->ha.delta) 535 pos = pos->next_in; 536 } 537 if ( (0 == json_array_size (hc->history)) && 538 (! h->in_shutdown) && 539 (GNUNET_TIME_absolute_is_future (hc->timeout)) && 540 (0 < hc->ha.delta)) 541 { 542 TALER_FAKEBANK_start_lp_ (h, 543 connection, 544 hc->acc, 545 GNUNET_TIME_absolute_get_remaining (hc->timeout), 546 LP_CREDIT, 547 NULL); 548 GNUNET_assert (0 == 549 pthread_mutex_unlock (&h->big_lock)); 550 return MHD_YES; 551 } 552 in_shutdown = h->in_shutdown; 553 acc_payto_uri = hc->acc->payto_uri; 554 GNUNET_assert (0 == 555 pthread_mutex_unlock (&h->big_lock)); 556 finish: 557 if (0 == json_array_size (hc->history)) 558 { 559 GNUNET_break (in_shutdown || 560 (! GNUNET_TIME_absolute_is_future (hc->timeout))); 561 return TALER_MHD_reply_static (connection, 562 MHD_HTTP_NO_CONTENT, 563 NULL, 564 NULL, 565 0); 566 } 567 { 568 json_t *jh = hc->history; 569 570 hc->history = NULL; 571 return TALER_MHD_REPLY_JSON_PACK ( 572 connection, 573 MHD_HTTP_OK, 574 GNUNET_JSON_pack_string ( 575 "credit_account", 576 acc_payto_uri), 577 GNUNET_JSON_pack_array_steal ( 578 "incoming_transactions", 579 jh)); 580 } 581 }