fakebank_twg_history.c (18249B)
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 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 = (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_in; 224 if (0 < hc->ha.delta) 225 pos = pos->next_in; 226 continue; 227 } 228 GNUNET_asprintf (&credit_payto, 229 "payto://x-taler-bank/localhost/%s?receiver-name=%s", 230 pos->credit_account->account_name, 231 pos->credit_account->receiver_name); 232 233 trans = GNUNET_JSON_PACK ( 234 GNUNET_JSON_pack_uint64 ("row_id", 235 pos->row_id), 236 GNUNET_JSON_pack_timestamp ("date", 237 pos->date), 238 TALER_JSON_pack_amount ("amount", 239 &pos->amount), 240 GNUNET_JSON_pack_string ("credit_account", 241 credit_payto), 242 GNUNET_JSON_pack_string ("exchange_base_url", 243 pos->subject.debit.exchange_base_url), 244 GNUNET_JSON_pack_data_auto ("wtid", 245 &pos->subject.debit.wtid)); 246 GNUNET_assert (NULL != trans); 247 GNUNET_free (credit_payto); 248 GNUNET_assert (0 == 249 json_array_append_new (hc->history, 250 trans)); 251 if (hc->ha.delta > 0) 252 hc->ha.delta--; 253 else 254 hc->ha.delta++; 255 if (0 > hc->ha.delta) 256 pos = pos->prev_out; 257 if (0 < hc->ha.delta) 258 pos = pos->next_out; 259 } 260 if ( (0 == json_array_size (hc->history)) && 261 (! h->in_shutdown) && 262 (GNUNET_TIME_absolute_is_future (hc->timeout)) && 263 (0 < hc->ha.delta)) 264 { 265 TALER_FAKEBANK_start_lp_ (h, 266 connection, 267 hc->acc, 268 GNUNET_TIME_absolute_get_remaining (hc->timeout), 269 LP_DEBIT, 270 NULL); 271 GNUNET_assert (0 == 272 pthread_mutex_unlock (&h->big_lock)); 273 return MHD_YES; 274 } 275 in_shutdown = h->in_shutdown; 276 acc_payto_uri = hc->acc->payto_uri; 277 GNUNET_assert (0 == 278 pthread_mutex_unlock (&h->big_lock)); 279 finish: 280 if (0 == json_array_size (hc->history)) 281 { 282 GNUNET_break (in_shutdown || 283 (! GNUNET_TIME_absolute_is_future (hc->timeout))); 284 return TALER_MHD_reply_static (connection, 285 MHD_HTTP_NO_CONTENT, 286 NULL, 287 NULL, 288 0); 289 } 290 { 291 json_t *jh = hc->history; 292 293 hc->history = NULL; 294 return TALER_MHD_REPLY_JSON_PACK ( 295 connection, 296 MHD_HTTP_OK, 297 GNUNET_JSON_pack_string ( 298 "debit_account", 299 acc_payto_uri), 300 GNUNET_JSON_pack_array_steal ( 301 "outgoing_transactions", 302 jh)); 303 } 304 } 305 306 307 MHD_RESULT 308 TALER_FAKEBANK_twg_get_credit_history_ ( 309 struct TALER_FAKEBANK_Handle *h, 310 struct MHD_Connection *connection, 311 const char *account, 312 void **con_cls) 313 { 314 struct ConnectionContext *cc = *con_cls; 315 struct HistoryContext *hc; 316 const struct Transaction *pos; 317 enum GNUNET_GenericReturnValue ret; 318 bool in_shutdown; 319 const char *acc_payto_uri; 320 321 if (NULL == cc) 322 { 323 cc = GNUNET_new (struct ConnectionContext); 324 cc->ctx_cleaner = &history_cleanup; 325 *con_cls = cc; 326 hc = GNUNET_new (struct HistoryContext); 327 cc->ctx = hc; 328 hc->history = json_array (); 329 if (NULL == hc->history) 330 { 331 GNUNET_break (0); 332 return MHD_NO; 333 } 334 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 335 "Handling /history/incoming connection %p\n", 336 connection); 337 if (GNUNET_OK != 338 (ret = TALER_FAKEBANK_common_parse_history_args (h, 339 connection, 340 &hc->ha))) 341 { 342 GNUNET_break_op (0); 343 return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES; 344 } 345 hc->timeout = GNUNET_TIME_relative_to_absolute (hc->ha.lp_timeout); 346 GNUNET_assert (0 == 347 pthread_mutex_lock (&h->big_lock)); 348 if (UINT64_MAX == hc->ha.start_idx) 349 hc->ha.start_idx = h->serial_counter; 350 hc->acc = TALER_FAKEBANK_lookup_account_ (h, 351 account, 352 NULL); 353 if (NULL == hc->acc) 354 { 355 GNUNET_assert (0 == 356 pthread_mutex_unlock (&h->big_lock)); 357 return TALER_MHD_reply_with_error (connection, 358 MHD_HTTP_NOT_FOUND, 359 TALER_EC_BANK_UNKNOWN_ACCOUNT, 360 account); 361 } 362 } 363 else 364 { 365 hc = cc->ctx; 366 GNUNET_assert (0 == 367 pthread_mutex_lock (&h->big_lock)); 368 } 369 370 if (! hc->ha.have_start) 371 { 372 pos = (0 > hc->ha.delta) 373 ? hc->acc->in_tail 374 : hc->acc->in_head; 375 } 376 else 377 { 378 struct Transaction *t = h->transactions[hc->ha.start_idx % h->ram_limit]; 379 bool overflow; 380 uint64_t dir; 381 bool skip = true; 382 383 overflow = ( (NULL != t) && (t->row_id != hc->ha.start_idx) ); 384 dir = (0 > hc->ha.delta) ? (h->ram_limit - 1) : 1; 385 /* If account does not match, linear scan for 386 first matching account. */ 387 while ( (! overflow) && 388 (NULL != t) && 389 (t->credit_account != hc->acc) ) 390 { 391 skip = false; 392 t = h->transactions[(t->row_id + dir) % h->ram_limit]; 393 if ( (NULL != t) && 394 (t->row_id == hc->ha.start_idx) ) 395 overflow = true; /* full circle, give up! */ 396 } 397 if ( (NULL == t) || 398 overflow) 399 { 400 in_shutdown = h->in_shutdown; 401 /* FIXME: these conditions are unclear to me. */ 402 if (GNUNET_TIME_relative_is_zero (hc->ha.lp_timeout) && 403 (0 < hc->ha.delta)) 404 { 405 acc_payto_uri = hc->acc->payto_uri; 406 GNUNET_assert (0 == 407 pthread_mutex_unlock (&h->big_lock)); 408 if (overflow) 409 return TALER_MHD_reply_with_ec ( 410 connection, 411 TALER_EC_BANK_ANCIENT_TRANSACTION_GONE, 412 NULL); 413 goto finish; 414 } 415 if (in_shutdown) 416 { 417 acc_payto_uri = hc->acc->payto_uri; 418 GNUNET_assert (0 == 419 pthread_mutex_unlock (&h->big_lock)); 420 goto finish; 421 } 422 TALER_FAKEBANK_start_lp_ (h, 423 connection, 424 hc->acc, 425 GNUNET_TIME_absolute_get_remaining ( 426 hc->timeout), 427 LP_CREDIT, 428 NULL); 429 GNUNET_assert (0 == 430 pthread_mutex_unlock (&h->big_lock)); 431 return MHD_YES; 432 } 433 if (skip) 434 { 435 /* range from application is exclusive, skip the 436 matching entry */ 437 if (0 > hc->ha.delta) 438 pos = t->prev_in; 439 else 440 pos = t->next_in; 441 } 442 else 443 { 444 pos = t; 445 } 446 } 447 if (NULL != pos) 448 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 449 "Returning %lld credit transactions starting (inclusive) from %llu\n", 450 (long long) hc->ha.delta, 451 (unsigned long long) pos->row_id); 452 while ( (0 != hc->ha.delta) && 453 (NULL != pos) ) 454 { 455 json_t *trans; 456 457 if ( (T_CREDIT != pos->type) && 458 (T_AUTH != pos->type) ) 459 { 460 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 461 "Unexpected DEBIT transaction #%llu for account `%s'\n", 462 (unsigned long long) pos->row_id, 463 account); 464 if (0 > hc->ha.delta) 465 pos = pos->prev_in; 466 if (0 < hc->ha.delta) 467 pos = pos->next_in; 468 continue; 469 } 470 switch (pos->type) 471 { 472 case T_DEBIT: 473 GNUNET_assert (0); 474 break; 475 case T_WAD: 476 trans = GNUNET_JSON_PACK ( 477 GNUNET_JSON_pack_string ("type", 478 "WAD"), 479 GNUNET_JSON_pack_uint64 ("row_id", 480 pos->row_id), 481 GNUNET_JSON_pack_timestamp ("date", 482 pos->date), 483 TALER_JSON_pack_amount ("amount", 484 &pos->amount), 485 GNUNET_JSON_pack_string ("debit_account", 486 pos->debit_account->payto_uri), 487 GNUNET_JSON_pack_string ("origin_exchange_url", 488 pos->subject.wad.origin_base_url), 489 GNUNET_JSON_pack_data_auto ("wad_id", 490 &pos->subject.wad.wad_id)); 491 break; 492 case T_CREDIT: 493 trans = GNUNET_JSON_PACK ( 494 GNUNET_JSON_pack_string ("type", 495 "RESERVE"), 496 GNUNET_JSON_pack_uint64 ("row_id", 497 pos->row_id), 498 GNUNET_JSON_pack_timestamp ("date", 499 pos->date), 500 TALER_JSON_pack_amount ("amount", 501 &pos->amount), 502 GNUNET_JSON_pack_string ("debit_account", 503 pos->debit_account->payto_uri), 504 GNUNET_JSON_pack_data_auto ("reserve_pub", 505 &pos->subject.credit.reserve_pub)); 506 break; 507 case T_AUTH: 508 trans = GNUNET_JSON_PACK ( 509 GNUNET_JSON_pack_string ("type", 510 "KYCAUTH"), 511 GNUNET_JSON_pack_uint64 ("row_id", 512 pos->row_id), 513 GNUNET_JSON_pack_timestamp ("date", 514 pos->date), 515 TALER_JSON_pack_amount ("amount", 516 &pos->amount), 517 GNUNET_JSON_pack_string ("debit_account", 518 pos->debit_account->payto_uri), 519 GNUNET_JSON_pack_data_auto ("account_pub", 520 &pos->subject.auth.account_pub)); 521 break; 522 } 523 GNUNET_assert (NULL != trans); 524 GNUNET_assert (0 == 525 json_array_append_new (hc->history, 526 trans)); 527 if (hc->ha.delta > 0) 528 hc->ha.delta--; 529 else 530 hc->ha.delta++; 531 if (0 > hc->ha.delta) 532 pos = pos->prev_in; 533 if (0 < hc->ha.delta) 534 pos = pos->next_in; 535 } 536 if ( (0 == json_array_size (hc->history)) && 537 (! h->in_shutdown) && 538 (GNUNET_TIME_absolute_is_future (hc->timeout)) && 539 (0 < hc->ha.delta)) 540 { 541 TALER_FAKEBANK_start_lp_ (h, 542 connection, 543 hc->acc, 544 GNUNET_TIME_absolute_get_remaining (hc->timeout), 545 LP_CREDIT, 546 NULL); 547 GNUNET_assert (0 == 548 pthread_mutex_unlock (&h->big_lock)); 549 return MHD_YES; 550 } 551 in_shutdown = h->in_shutdown; 552 acc_payto_uri = hc->acc->payto_uri; 553 GNUNET_assert (0 == 554 pthread_mutex_unlock (&h->big_lock)); 555 finish: 556 if (0 == json_array_size (hc->history)) 557 { 558 GNUNET_break (in_shutdown || 559 (! GNUNET_TIME_absolute_is_future (hc->timeout))); 560 return TALER_MHD_reply_static (connection, 561 MHD_HTTP_NO_CONTENT, 562 NULL, 563 NULL, 564 0); 565 } 566 { 567 json_t *jh = hc->history; 568 569 hc->history = NULL; 570 return TALER_MHD_REPLY_JSON_PACK ( 571 connection, 572 MHD_HTTP_OK, 573 GNUNET_JSON_pack_string ( 574 "credit_account", 575 acc_payto_uri), 576 GNUNET_JSON_pack_array_steal ( 577 "incoming_transactions", 578 jh)); 579 } 580 }