fakebank_twg_history.c (18277B)
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 "taler/platform.h" 25 #include <pthread.h> 26 #include "taler/taler_fakebank_lib.h" 27 #include "taler/taler_bank_service.h" 28 #include "taler/taler_mhd_lib.h" 29 #include <gnunet/gnunet_mhd_compat.h> 30 #include "fakebank.h" 31 #include "fakebank_twg_history.h" 32 #include "fakebank_common_lookup.h" 33 #include "fakebank_common_lp.h" 34 #include "fakebank_common_parser.h" 35 36 /** 37 * Function called to clean up a history context. 38 * 39 * @param cls a `struct HistoryContext *` 40 */ 41 static void 42 history_cleanup (void *cls) 43 { 44 struct HistoryContext *hc = cls; 45 46 json_decref (hc->history); 47 GNUNET_free (hc); 48 } 49 50 51 MHD_RESULT 52 TALER_FAKEBANK_twg_get_debit_history_ ( 53 struct TALER_FAKEBANK_Handle *h, 54 struct MHD_Connection *connection, 55 const char *account, 56 void **con_cls) 57 { 58 struct ConnectionContext *cc = *con_cls; 59 struct HistoryContext *hc; 60 struct Transaction *pos; 61 enum GNUNET_GenericReturnValue ret; 62 bool in_shutdown; 63 const char *acc_payto_uri; 64 65 if (NULL == cc) 66 { 67 cc = GNUNET_new (struct ConnectionContext); 68 cc->ctx_cleaner = &history_cleanup; 69 *con_cls = cc; 70 hc = GNUNET_new (struct HistoryContext); 71 cc->ctx = hc; 72 73 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 74 "Handling /history/outgoing connection %p\n", 75 connection); 76 if (GNUNET_OK != 77 (ret = TALER_FAKEBANK_common_parse_history_args (h, 78 connection, 79 &hc->ha))) 80 { 81 GNUNET_break_op (0); 82 return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES; 83 } 84 GNUNET_assert (0 == 85 pthread_mutex_lock (&h->big_lock)); 86 if (UINT64_MAX == hc->ha.start_idx) 87 hc->ha.start_idx = h->serial_counter; 88 hc->acc = TALER_FAKEBANK_lookup_account_ (h, 89 account, 90 NULL); 91 if (NULL == hc->acc) 92 { 93 GNUNET_assert (0 == 94 pthread_mutex_unlock (&h->big_lock)); 95 return TALER_MHD_reply_with_error (connection, 96 MHD_HTTP_NOT_FOUND, 97 TALER_EC_BANK_UNKNOWN_ACCOUNT, 98 account); 99 } 100 hc->history = json_array (); 101 if (NULL == hc->history) 102 { 103 GNUNET_break (0); 104 GNUNET_assert (0 == 105 pthread_mutex_unlock (&h->big_lock)); 106 return MHD_NO; 107 } 108 hc->timeout = GNUNET_TIME_relative_to_absolute (hc->ha.lp_timeout); 109 } 110 else 111 { 112 hc = cc->ctx; 113 GNUNET_assert (0 == 114 pthread_mutex_lock (&h->big_lock)); 115 } 116 117 if (! hc->ha.have_start) 118 { 119 pos = (0 > hc->ha.delta) 120 ? hc->acc->out_tail 121 : hc->acc->out_head; 122 } 123 else 124 { 125 struct Transaction *t = h->transactions[hc->ha.start_idx % h->ram_limit]; 126 bool overflow; 127 uint64_t dir; 128 bool skip = true; 129 130 dir = (0 > hc->ha.delta) ? (h->ram_limit - 1) : 1; 131 overflow = (t->row_id != hc->ha.start_idx); 132 /* If account does not match, linear scan for 133 first matching account. */ 134 while ( (! overflow) && 135 (NULL != t) && 136 (t->debit_account != hc->acc) ) 137 { 138 skip = false; 139 t = h->transactions[(t->row_id + dir) % h->ram_limit]; 140 if ( (NULL != t) && 141 (t->row_id == hc->ha.start_idx) ) 142 overflow = true; /* full circle, give up! */ 143 } 144 if ( (NULL == t) || 145 overflow) 146 { 147 /* FIXME: these conditions are unclear to me. */ 148 if ( (GNUNET_TIME_relative_is_zero (hc->ha.lp_timeout)) && 149 (0 < hc->ha.delta)) 150 { 151 acc_payto_uri = hc->acc->payto_uri; 152 in_shutdown = h->in_shutdown; 153 GNUNET_assert (0 == 154 pthread_mutex_unlock (&h->big_lock)); 155 if (overflow) 156 { 157 return TALER_MHD_reply_with_ec ( 158 connection, 159 TALER_EC_BANK_ANCIENT_TRANSACTION_GONE, 160 NULL); 161 } 162 goto finish; 163 } 164 if (h->in_shutdown) 165 { 166 acc_payto_uri = hc->acc->payto_uri; 167 in_shutdown = h->in_shutdown; 168 GNUNET_assert (0 == 169 pthread_mutex_unlock (&h->big_lock)); 170 goto finish; 171 } 172 TALER_FAKEBANK_start_lp_ (h, 173 connection, 174 hc->acc, 175 GNUNET_TIME_absolute_get_remaining ( 176 hc->timeout), 177 LP_DEBIT, 178 NULL); 179 GNUNET_assert (0 == 180 pthread_mutex_unlock (&h->big_lock)); 181 return MHD_YES; 182 } 183 if (t->debit_account != hc->acc) 184 { 185 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 186 "Invalid start specified, transaction %llu not with account %s!\n", 187 (unsigned long long) hc->ha.start_idx, 188 account); 189 GNUNET_assert (0 == 190 pthread_mutex_unlock (&h->big_lock)); 191 return MHD_NO; 192 } 193 if (skip) 194 { 195 /* range is exclusive, skip the matching entry */ 196 if (0 > hc->ha.delta) 197 pos = t->prev_out; 198 else 199 pos = t->next_out; 200 } 201 else 202 { 203 pos = t; 204 } 205 } 206 if (NULL != pos) 207 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 208 "Returning %lld debit transactions starting (inclusive) from %llu\n", 209 (long long) hc->ha.delta, 210 (unsigned long long) pos->row_id); 211 while ( (0 != hc->ha.delta) && 212 (NULL != pos) ) 213 { 214 json_t *trans; 215 char *credit_payto; 216 217 if (T_DEBIT != pos->type) 218 { 219 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 220 "Unexpected CREDIT transaction #%llu for account `%s'\n", 221 (unsigned long long) pos->row_id, 222 account); 223 if (0 > hc->ha.delta) 224 pos = pos->prev_in; 225 if (0 < hc->ha.delta) 226 pos = pos->next_in; 227 continue; 228 } 229 GNUNET_asprintf (&credit_payto, 230 "payto://x-taler-bank/localhost/%s?receiver-name=%s", 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 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 }