LCOV - code coverage report
Current view: top level - exchangedb - get_reserve_history.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 53.1 % 311 165
Test Date: 2026-04-14 15:39:31 Functions: 60.0 % 10 6

            Line data    Source code
       1              : /*
       2              :    This file is part of TALER
       3              :    Copyright (C) 2022-2023 Taler Systems SA
       4              : 
       5              :    TALER is free software; you can redistribute it and/or modify it under the
       6              :    terms of the GNU 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 General Public License for more details.
      12              : 
      13              :    You should have received a copy of the GNU General Public License along with
      14              :    TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
      15              :  */
      16              : /**
      17              :  * @file get_reserve_history.c
      18              :  * @brief Obtain (parts of) the history of a reserve.
      19              :  * @author Christian Grothoff
      20              :  */
      21              : #include "taler/taler_error_codes.h"
      22              : #include "taler/taler_pq_lib.h"
      23              : #include "exchange-database/get_reserve_history.h"
      24              : #include "exchange-database/start_read_committed.h"
      25              : #include "exchange-database/commit.h"
      26              : #include "exchange-database/rollback.h"
      27              : #include "helper.h"
      28              : 
      29              : /**
      30              :  * How often do we re-try when encountering DB serialization issues?
      31              :  * (We are read-only, so can only happen due to concurrent insert,
      32              :  * which should be very rare.)
      33              :  */
      34              : #define RETRIES 3
      35              : 
      36              : 
      37              : /**
      38              :  * Closure for callbacks invoked via #TALER_EXCHANGEDB_get_reserve_history().
      39              :  */
      40              : struct ReserveHistoryContext
      41              : {
      42              : 
      43              :   /**
      44              :    * Which reserve are we building the history for?
      45              :    */
      46              :   const struct TALER_ReservePublicKeyP *reserve_pub;
      47              : 
      48              :   /**
      49              :    * Where we build the history.
      50              :    */
      51              :   struct TALER_EXCHANGEDB_ReserveHistory *rh;
      52              : 
      53              :   /**
      54              :    * Tail of @e rh list.
      55              :    */
      56              :   struct TALER_EXCHANGEDB_ReserveHistory *rh_tail;
      57              : 
      58              :   /**
      59              :    * Plugin context.
      60              :    */
      61              :   struct TALER_EXCHANGEDB_PostgresContext *pg;
      62              : 
      63              :   /**
      64              :    * Sum of all credit transactions.
      65              :    */
      66              :   struct TALER_Amount balance_in;
      67              : 
      68              :   /**
      69              :    * Sum of all debit transactions.
      70              :    */
      71              :   struct TALER_Amount balance_out;
      72              : 
      73              :   /**
      74              :    * Current reserve_history_serial_id being processed,
      75              :    * set before each sub-table callback.
      76              :    */
      77              :   uint64_t current_history_offset;
      78              : 
      79              :   /**
      80              :    * Set to true on serious internal errors during
      81              :    * the callbacks.
      82              :    */
      83              :   bool failed;
      84              : };
      85              : 
      86              : 
      87              : /**
      88              :  * Append and return a fresh element to the reserve
      89              :  * history kept in @a rhc.
      90              :  *
      91              :  * @param rhc where the history is kept
      92              :  * @return the fresh element that was added
      93              :  */
      94              : static struct TALER_EXCHANGEDB_ReserveHistory *
      95           23 : append_rh (struct ReserveHistoryContext *rhc)
      96              : {
      97              :   struct TALER_EXCHANGEDB_ReserveHistory *tail;
      98              : 
      99           23 :   tail = GNUNET_new (struct TALER_EXCHANGEDB_ReserveHistory);
     100           23 :   tail->history_offset = rhc->current_history_offset;
     101           23 :   if (NULL != rhc->rh_tail)
     102              :   {
     103           15 :     rhc->rh_tail->next = tail;
     104           15 :     rhc->rh_tail = tail;
     105              :   }
     106              :   else
     107              :   {
     108            8 :     rhc->rh_tail = tail;
     109            8 :     rhc->rh = tail;
     110              :   }
     111           23 :   return tail;
     112              : }
     113              : 
     114              : 
     115              : /**
     116              :  * Add bank transfers to result set for #TALER_EXCHANGEDB_get_reserve_history.
     117              :  *
     118              :  * @param cls a `struct ReserveHistoryContext *`
     119              :  * @param result SQL result
     120              :  * @param num_results number of rows in @a result
     121              :  */
     122              : static void
     123            8 : add_bank_to_exchange (void *cls,
     124              :                       PGresult *result,
     125              :                       unsigned int num_results)
     126              : {
     127            8 :   struct ReserveHistoryContext *rhc = cls;
     128            8 :   struct TALER_EXCHANGEDB_PostgresContext *pg = rhc->pg;
     129              : 
     130           16 :   while (0 < num_results)
     131              :   {
     132              :     struct TALER_EXCHANGEDB_BankTransfer *bt;
     133              :     struct TALER_EXCHANGEDB_ReserveHistory *tail;
     134              : 
     135            8 :     bt = GNUNET_new (struct TALER_EXCHANGEDB_BankTransfer);
     136              :     {
     137            8 :       struct GNUNET_PQ_ResultSpec rs[] = {
     138            8 :         GNUNET_PQ_result_spec_uint64 ("wire_reference",
     139              :                                       &bt->wire_reference),
     140            8 :         TALER_PQ_RESULT_SPEC_AMOUNT ("credit",
     141              :                                      &bt->amount),
     142            8 :         GNUNET_PQ_result_spec_timestamp ("execution_date",
     143              :                                          &bt->execution_date),
     144            8 :         GNUNET_PQ_result_spec_string ("sender_account_details",
     145              :                                       &bt->sender_account_details.full_payto),
     146              :         GNUNET_PQ_result_spec_end
     147              :       };
     148              : 
     149            8 :       if (GNUNET_OK !=
     150            8 :           GNUNET_PQ_extract_result (result,
     151              :                                     rs,
     152              :                                     --num_results))
     153              :       {
     154            0 :         GNUNET_break (0);
     155            0 :         GNUNET_free (bt);
     156            0 :         rhc->failed = true;
     157            0 :         return;
     158              :       }
     159              :     }
     160            8 :     GNUNET_assert (0 <=
     161              :                    TALER_amount_add (&rhc->balance_in,
     162              :                                      &rhc->balance_in,
     163              :                                      &bt->amount));
     164            8 :     bt->reserve_pub = *rhc->reserve_pub;
     165            8 :     tail = append_rh (rhc);
     166            8 :     tail->type = TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE;
     167            8 :     tail->details.bank = bt;
     168              :   } /* end of 'while (0 < rows)' */
     169              : }
     170              : 
     171              : 
     172              : /**
     173              :  * Add coin withdrawals to result set for #TALER_EXCHANGEDB_get_reserve_history.
     174              :  *
     175              :  * @param cls a `struct ReserveHistoryContext *`
     176              :  * @param result SQL result
     177              :  * @param num_results number of rows in @a result
     178              :  */
     179              : static void
     180            7 : add_withdraw (void *cls,
     181              :               PGresult *result,
     182              :               unsigned int num_results)
     183              : {
     184            7 :   struct ReserveHistoryContext *rhc = cls;
     185            7 :   struct TALER_EXCHANGEDB_PostgresContext *pg = rhc->pg;
     186              : 
     187           14 :   while (0 < num_results)
     188              :   {
     189              :     struct TALER_EXCHANGEDB_Withdraw *wd;
     190              :     struct TALER_EXCHANGEDB_ReserveHistory *tail;
     191              : 
     192            7 :     wd = GNUNET_new (struct TALER_EXCHANGEDB_Withdraw);
     193              :     {
     194              :       bool no_noreveal_index;
     195              :       bool no_max_age;
     196              :       bool no_selected_h;
     197              :       size_t num_denom_hs;
     198              :       size_t num_denom_serials;
     199            7 :       uint64_t *my_denom_serials = NULL;
     200            7 :       struct TALER_DenominationHashP *my_denom_pub_hashes = NULL;
     201            7 :       struct GNUNET_PQ_ResultSpec rs[] = {
     202            7 :         GNUNET_PQ_result_spec_auto_from_type  ("planchets_h",
     203              :                                                &wd->planchets_h),
     204            7 :         GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
     205              :                                               &wd->reserve_sig),
     206            7 :         TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
     207              :                                      &wd->amount_with_fee),
     208            7 :         GNUNET_PQ_result_spec_allow_null (
     209              :           GNUNET_PQ_result_spec_uint16 ("max_age",
     210              :                                         &wd->max_age),
     211              :           &no_max_age),
     212            7 :         GNUNET_PQ_result_spec_allow_null (
     213              :           GNUNET_PQ_result_spec_uint16 ("noreveal_index",
     214              :                                         &wd->noreveal_index),
     215              :           &no_noreveal_index),
     216            7 :         GNUNET_PQ_result_spec_allow_null (
     217            7 :           GNUNET_PQ_result_spec_auto_from_type ("blinding_seed",
     218              :                                                 &wd->blinding_seed),
     219              :           &wd->no_blinding_seed),
     220            7 :         GNUNET_PQ_result_spec_allow_null (
     221            7 :           GNUNET_PQ_result_spec_auto_from_type ("selected_h",
     222              :                                                 &wd->selected_h),
     223              :           &no_selected_h),
     224            7 :         TALER_PQ_result_spec_array_denom_hash (pg->conn,
     225              :                                                "denom_pub_hashes",
     226              :                                                &num_denom_hs,
     227              :                                                &my_denom_pub_hashes),
     228            7 :         GNUNET_PQ_result_spec_array_uint64 (pg->conn,
     229              :                                             "denom_serials",
     230              :                                             &num_denom_serials,
     231              :                                             &my_denom_serials),
     232              :         GNUNET_PQ_result_spec_end
     233              :       };
     234              : 
     235            7 :       if (GNUNET_OK !=
     236            7 :           GNUNET_PQ_extract_result (result,
     237              :                                     rs,
     238              :                                     --num_results))
     239              :       {
     240            0 :         GNUNET_break (0);
     241            0 :         GNUNET_free (wd);
     242            0 :         rhc->failed = true;
     243            0 :         GNUNET_PQ_cleanup_result (rs);
     244            0 :         return;
     245              :       }
     246              : 
     247            7 :       if (num_denom_hs != num_denom_serials)
     248              :       {
     249            0 :         GNUNET_break (0);
     250            0 :         GNUNET_free (wd);
     251            0 :         rhc->failed = true;
     252            0 :         GNUNET_PQ_cleanup_result (rs);
     253            0 :         return;
     254              :       }
     255              : 
     256            7 :       if ((no_noreveal_index != no_max_age) ||
     257            7 :           (no_noreveal_index != no_selected_h))
     258              :       {
     259            0 :         GNUNET_break (0);
     260            0 :         GNUNET_free (wd);
     261            0 :         rhc->failed = true;
     262            0 :         GNUNET_PQ_cleanup_result (rs);
     263            0 :         return;
     264              :       }
     265            7 :       wd->age_proof_required = ! no_max_age;
     266            7 :       wd->num_coins = num_denom_serials;
     267            7 :       wd->reserve_pub = *rhc->reserve_pub;
     268            7 :       wd->denom_serials = my_denom_serials;
     269            7 :       wd->denom_pub_hashes = my_denom_pub_hashes;
     270              :       /* prevent cleanup from destroying our actual result */
     271            7 :       my_denom_serials = NULL;
     272            7 :       my_denom_pub_hashes = NULL;
     273            7 :       GNUNET_PQ_cleanup_result (rs);
     274              :     }
     275              : 
     276            7 :     tail = append_rh (rhc);
     277            7 :     tail->type = TALER_EXCHANGEDB_RO_WITHDRAW_COINS;
     278            7 :     tail->details.withdraw = wd;
     279              :   }
     280              : }
     281              : 
     282              : 
     283              : /**
     284              :  * Add recoups to result set for #TALER_EXCHANGEDB_get_reserve_history.
     285              :  *
     286              :  * @param cls a `struct ReserveHistoryContext *`
     287              :  * @param result SQL result
     288              :  * @param num_results number of rows in @a result
     289              :  */
     290              : static void
     291            0 : add_recoup (void *cls,
     292              :             PGresult *result,
     293              :             unsigned int num_results)
     294              : {
     295            0 :   struct ReserveHistoryContext *rhc = cls;
     296            0 :   struct TALER_EXCHANGEDB_PostgresContext *pg = rhc->pg;
     297              : 
     298            0 :   while (0 < num_results)
     299              :   {
     300              :     struct TALER_EXCHANGEDB_Recoup *recoup;
     301              :     struct TALER_EXCHANGEDB_ReserveHistory *tail;
     302              : 
     303            0 :     recoup = GNUNET_new (struct TALER_EXCHANGEDB_Recoup);
     304              :     {
     305            0 :       struct GNUNET_PQ_ResultSpec rs[] = {
     306            0 :         TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
     307              :                                      &recoup->value),
     308            0 :         GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
     309              :                                               &recoup->coin.coin_pub),
     310            0 :         GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
     311              :                                               &recoup->coin_blind),
     312            0 :         GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
     313              :                                               &recoup->coin_sig),
     314            0 :         GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
     315              :                                          &recoup->timestamp),
     316            0 :         GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
     317              :                                               &recoup->coin.denom_pub_hash),
     318            0 :         TALER_PQ_result_spec_denom_sig (
     319              :           "denom_sig",
     320              :           &recoup->coin.denom_sig),
     321              :         GNUNET_PQ_result_spec_end
     322              :       };
     323              : 
     324            0 :       if (GNUNET_OK !=
     325            0 :           GNUNET_PQ_extract_result (result,
     326              :                                     rs,
     327              :                                     --num_results))
     328              :       {
     329            0 :         GNUNET_break (0);
     330            0 :         GNUNET_free (recoup);
     331            0 :         rhc->failed = true;
     332            0 :         return;
     333              :       }
     334              :     }
     335            0 :     GNUNET_assert (0 <=
     336              :                    TALER_amount_add (&rhc->balance_in,
     337              :                                      &rhc->balance_in,
     338              :                                      &recoup->value));
     339            0 :     recoup->reserve_pub = *rhc->reserve_pub;
     340            0 :     tail = append_rh (rhc);
     341            0 :     tail->type = TALER_EXCHANGEDB_RO_RECOUP_COIN;
     342            0 :     tail->details.recoup = recoup;
     343              :   } /* end of 'while (0 < rows)' */
     344              : }
     345              : 
     346              : 
     347              : /**
     348              :  * Add exchange-to-bank transfers to result set for
     349              :  * #TALER_EXCHANGEDB_get_reserve_history.
     350              :  *
     351              :  * @param cls a `struct ReserveHistoryContext *`
     352              :  * @param result SQL result
     353              :  * @param num_results number of rows in @a result
     354              :  */
     355              : static void
     356            0 : add_exchange_to_bank (void *cls,
     357              :                       PGresult *result,
     358              :                       unsigned int num_results)
     359              : {
     360            0 :   struct ReserveHistoryContext *rhc = cls;
     361            0 :   struct TALER_EXCHANGEDB_PostgresContext *pg = rhc->pg;
     362              : 
     363            0 :   while (0 < num_results)
     364              :   {
     365              :     struct TALER_EXCHANGEDB_ClosingTransfer *closing;
     366              :     struct TALER_EXCHANGEDB_ReserveHistory *tail;
     367              : 
     368            0 :     closing = GNUNET_new (struct TALER_EXCHANGEDB_ClosingTransfer);
     369              :     {
     370            0 :       struct GNUNET_PQ_ResultSpec rs[] = {
     371            0 :         TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
     372              :                                      &closing->amount),
     373            0 :         TALER_PQ_RESULT_SPEC_AMOUNT ("closing_fee",
     374              :                                      &closing->closing_fee),
     375            0 :         GNUNET_PQ_result_spec_timestamp ("execution_date",
     376              :                                          &closing->execution_date),
     377            0 :         GNUNET_PQ_result_spec_string ("receiver_account",
     378              :                                       &closing->receiver_account_details.
     379              :                                       full_payto),
     380            0 :         GNUNET_PQ_result_spec_auto_from_type ("wtid",
     381              :                                               &closing->wtid),
     382              :         GNUNET_PQ_result_spec_end
     383              :       };
     384              : 
     385            0 :       if (GNUNET_OK !=
     386            0 :           GNUNET_PQ_extract_result (result,
     387              :                                     rs,
     388              :                                     --num_results))
     389              :       {
     390            0 :         GNUNET_break (0);
     391            0 :         GNUNET_free (closing);
     392            0 :         rhc->failed = true;
     393            0 :         return;
     394              :       }
     395              :     }
     396            0 :     GNUNET_assert (0 <=
     397              :                    TALER_amount_add (&rhc->balance_out,
     398              :                                      &rhc->balance_out,
     399              :                                      &closing->amount));
     400            0 :     closing->reserve_pub = *rhc->reserve_pub;
     401            0 :     tail = append_rh (rhc);
     402            0 :     tail->type = TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK;
     403            0 :     tail->details.closing = closing;
     404              :   } /* end of 'while (0 < rows)' */
     405              : }
     406              : 
     407              : 
     408              : /**
     409              :  * Add purse merge transfers to result set for
     410              :  * #TALER_EXCHANGEDB_get_reserve_history.
     411              :  *
     412              :  * @param cls a `struct ReserveHistoryContext *`
     413              :  * @param result SQL result
     414              :  * @param num_results number of rows in @a result
     415              :  */
     416              : static void
     417            8 : add_p2p_merge (void *cls,
     418              :                PGresult *result,
     419              :                unsigned int num_results)
     420              : {
     421            8 :   struct ReserveHistoryContext *rhc = cls;
     422            8 :   struct TALER_EXCHANGEDB_PostgresContext *pg = rhc->pg;
     423              : 
     424           16 :   while (0 < num_results)
     425              :   {
     426              :     struct TALER_EXCHANGEDB_PurseMerge *merge;
     427              :     struct TALER_EXCHANGEDB_ReserveHistory *tail;
     428              : 
     429            8 :     merge = GNUNET_new (struct TALER_EXCHANGEDB_PurseMerge);
     430              :     {
     431              :       uint32_t flags32;
     432              :       struct TALER_Amount balance;
     433            8 :       struct GNUNET_PQ_ResultSpec rs[] = {
     434            8 :         TALER_PQ_RESULT_SPEC_AMOUNT ("purse_fee",
     435              :                                      &merge->purse_fee),
     436            8 :         TALER_PQ_RESULT_SPEC_AMOUNT ("balance",
     437              :                                      &balance),
     438            8 :         TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
     439              :                                      &merge->amount_with_fee),
     440            8 :         GNUNET_PQ_result_spec_timestamp ("merge_timestamp",
     441              :                                          &merge->merge_timestamp),
     442            8 :         GNUNET_PQ_result_spec_timestamp ("purse_expiration",
     443              :                                          &merge->purse_expiration),
     444            8 :         GNUNET_PQ_result_spec_uint32 ("age_limit",
     445              :                                       &merge->min_age),
     446            8 :         GNUNET_PQ_result_spec_uint32 ("flags",
     447              :                                       &flags32),
     448            8 :         GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
     449              :                                               &merge->h_contract_terms),
     450            8 :         GNUNET_PQ_result_spec_auto_from_type ("merge_pub",
     451              :                                               &merge->merge_pub),
     452            8 :         GNUNET_PQ_result_spec_auto_from_type ("purse_pub",
     453              :                                               &merge->purse_pub),
     454            8 :         GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
     455              :                                               &merge->reserve_sig),
     456              :         GNUNET_PQ_result_spec_end
     457              :       };
     458              : 
     459            8 :       if (GNUNET_OK !=
     460            8 :           GNUNET_PQ_extract_result (result,
     461              :                                     rs,
     462              :                                     --num_results))
     463              :       {
     464            0 :         GNUNET_break (0);
     465            0 :         GNUNET_free (merge);
     466            0 :         rhc->failed = true;
     467            0 :         return;
     468              :       }
     469            8 :       merge->flags = (enum TALER_WalletAccountMergeFlags) flags32;
     470            8 :       if ( (! GNUNET_TIME_absolute_is_future (
     471            8 :               merge->merge_timestamp.abs_time)) &&
     472            8 :            (-1 != TALER_amount_cmp (&balance,
     473            8 :                                     &merge->amount_with_fee)) )
     474            8 :         merge->merged = true;
     475              :     }
     476            8 :     if (merge->merged)
     477            8 :       GNUNET_assert (0 <=
     478              :                      TALER_amount_add (&rhc->balance_in,
     479              :                                        &rhc->balance_in,
     480              :                                        &merge->amount_with_fee));
     481            8 :     GNUNET_assert (0 <=
     482              :                    TALER_amount_add (&rhc->balance_out,
     483              :                                      &rhc->balance_out,
     484              :                                      &merge->purse_fee));
     485            8 :     merge->reserve_pub = *rhc->reserve_pub;
     486            8 :     tail = append_rh (rhc);
     487            8 :     tail->type = TALER_EXCHANGEDB_RO_PURSE_MERGE;
     488            8 :     tail->details.merge = merge;
     489              :   }
     490              : }
     491              : 
     492              : 
     493              : /**
     494              :  * Add paid for history requests to result set for
     495              :  * #TALER_EXCHANGEDB_get_reserve_history.
     496              :  *
     497              :  * @param cls a `struct ReserveHistoryContext *`
     498              :  * @param result SQL result
     499              :  * @param num_results number of rows in @a result
     500              :  */
     501              : static void
     502            0 : add_open_requests (void *cls,
     503              :                    PGresult *result,
     504              :                    unsigned int num_results)
     505              : {
     506            0 :   struct ReserveHistoryContext *rhc = cls;
     507            0 :   struct TALER_EXCHANGEDB_PostgresContext *pg = rhc->pg;
     508              : 
     509            0 :   while (0 < num_results)
     510              :   {
     511              :     struct TALER_EXCHANGEDB_OpenRequest *orq;
     512              :     struct TALER_EXCHANGEDB_ReserveHistory *tail;
     513              : 
     514            0 :     orq = GNUNET_new (struct TALER_EXCHANGEDB_OpenRequest);
     515              :     {
     516            0 :       struct GNUNET_PQ_ResultSpec rs[] = {
     517            0 :         TALER_PQ_RESULT_SPEC_AMOUNT ("open_fee",
     518              :                                      &orq->open_fee),
     519            0 :         GNUNET_PQ_result_spec_timestamp ("request_timestamp",
     520              :                                          &orq->request_timestamp),
     521            0 :         GNUNET_PQ_result_spec_timestamp ("expiration_date",
     522              :                                          &orq->reserve_expiration),
     523            0 :         GNUNET_PQ_result_spec_uint32 ("requested_purse_limit",
     524              :                                       &orq->purse_limit),
     525            0 :         GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
     526              :                                               &orq->reserve_sig),
     527              :         GNUNET_PQ_result_spec_end
     528              :       };
     529              : 
     530            0 :       if (GNUNET_OK !=
     531            0 :           GNUNET_PQ_extract_result (result,
     532              :                                     rs,
     533              :                                     --num_results))
     534              :       {
     535            0 :         GNUNET_break (0);
     536            0 :         GNUNET_free (orq);
     537            0 :         rhc->failed = true;
     538            0 :         return;
     539              :       }
     540              :     }
     541            0 :     GNUNET_assert (0 <=
     542              :                    TALER_amount_add (&rhc->balance_out,
     543              :                                      &rhc->balance_out,
     544              :                                      &orq->open_fee));
     545            0 :     orq->reserve_pub = *rhc->reserve_pub;
     546            0 :     tail = append_rh (rhc);
     547            0 :     tail->type = TALER_EXCHANGEDB_RO_OPEN_REQUEST;
     548            0 :     tail->details.open_request = orq;
     549              :   }
     550              : }
     551              : 
     552              : 
     553              : /**
     554              :  * Add paid for history requests to result set for
     555              :  * #TALER_EXCHANGEDB_get_reserve_history.
     556              :  *
     557              :  * @param cls a `struct ReserveHistoryContext *`
     558              :  * @param result SQL result
     559              :  * @param num_results number of rows in @a result
     560              :  */
     561              : static void
     562            0 : add_close_requests (void *cls,
     563              :                     PGresult *result,
     564              :                     unsigned int num_results)
     565              : {
     566            0 :   struct ReserveHistoryContext *rhc = cls;
     567              : 
     568            0 :   while (0 < num_results)
     569              :   {
     570              :     struct TALER_EXCHANGEDB_CloseRequest *crq;
     571              :     struct TALER_EXCHANGEDB_ReserveHistory *tail;
     572              : 
     573            0 :     crq = GNUNET_new (struct TALER_EXCHANGEDB_CloseRequest);
     574              :     {
     575              :       struct TALER_FullPayto payto_uri;
     576            0 :       struct GNUNET_PQ_ResultSpec rs[] = {
     577            0 :         GNUNET_PQ_result_spec_timestamp ("close_timestamp",
     578              :                                          &crq->request_timestamp),
     579            0 :         GNUNET_PQ_result_spec_string ("payto_uri",
     580              :                                       &payto_uri.full_payto),
     581            0 :         GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
     582              :                                               &crq->reserve_sig),
     583              :         GNUNET_PQ_result_spec_end
     584              :       };
     585              : 
     586            0 :       if (GNUNET_OK !=
     587            0 :           GNUNET_PQ_extract_result (result,
     588              :                                     rs,
     589              :                                     --num_results))
     590              :       {
     591            0 :         GNUNET_break (0);
     592            0 :         GNUNET_free (crq);
     593            0 :         rhc->failed = true;
     594            0 :         return;
     595              :       }
     596            0 :       TALER_full_payto_hash (payto_uri,
     597              :                              &crq->target_account_h_payto);
     598            0 :       GNUNET_free (payto_uri.full_payto);
     599              :     }
     600            0 :     crq->reserve_pub = *rhc->reserve_pub;
     601            0 :     tail = append_rh (rhc);
     602            0 :     tail->type = TALER_EXCHANGEDB_RO_CLOSE_REQUEST;
     603            0 :     tail->details.close_request = crq;
     604              :   }
     605              : }
     606              : 
     607              : 
     608              : /**
     609              :  * Add reserve history entries found.
     610              :  *
     611              :  * @param cls a `struct ReserveHistoryContext *`
     612              :  * @param result SQL result
     613              :  * @param num_results number of rows in @a result
     614              :  */
     615              : static void
     616            8 : handle_history_entry (void *cls,
     617              :                       PGresult *result,
     618              :                       unsigned int num_results)
     619              : {
     620              :   static const struct
     621              :   {
     622              :     /**
     623              :      * Table with reserve history entry we are responsible for.
     624              :      */
     625              :     const char *table;
     626              :     /**
     627              :      * Name of the prepared statement to run.
     628              :      */
     629              :     const char *statement;
     630              :     /**
     631              :      * Function to use to process the results.
     632              :      */
     633              :     GNUNET_PQ_PostgresResultHandler cb;
     634              :   } work[] = {
     635              :     /** #TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE */
     636              :     { "reserves_in",
     637              :       "reserves_in_get_transactions",
     638              :       add_bank_to_exchange },
     639              :     /** #TALER_EXCHANGEDB_RO_WITHDRAW_COINS */
     640              :     { "withdraw",
     641              :       "get_withdraw_details",
     642              :       &add_withdraw },
     643              :     /** #TALER_EXCHANGEDB_RO_RECOUP_COIN */
     644              :     { "recoup",
     645              :       "recoup_by_reserve",
     646              :       &add_recoup },
     647              :     /** #TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK */
     648              :     { "reserves_close",
     649              :       "close_by_reserve",
     650              :       &add_exchange_to_bank },
     651              :     /** #TALER_EXCHANGEDB_RO_PURSE_MERGE */
     652              :     { "purse_decision",
     653              :       "merge_by_reserve",
     654              :       &add_p2p_merge },
     655              :     /** #TALER_EXCHANGEDB_RO_OPEN_REQUEST */
     656              :     { "reserves_open_requests",
     657              :       "open_request_by_reserve",
     658              :       &add_open_requests },
     659              :     /** #TALER_EXCHANGEDB_RO_CLOSE_REQUEST */
     660              :     { "close_requests",
     661              :       "close_request_by_reserve",
     662              :       &add_close_requests },
     663              :     /* List terminator */
     664              :     { NULL, NULL, NULL }
     665              :   };
     666            8 :   struct ReserveHistoryContext *rhc = cls;
     667              :   char *table_name;
     668              :   uint64_t serial_id;
     669            8 :   struct GNUNET_PQ_ResultSpec rs[] = {
     670            8 :     GNUNET_PQ_result_spec_string ("table_name",
     671              :                                   &table_name),
     672            8 :     GNUNET_PQ_result_spec_uint64 ("serial_id",
     673              :                                   &serial_id),
     674            8 :     GNUNET_PQ_result_spec_uint64 ("reserve_history_serial_id",
     675              :                                   &rhc->current_history_offset),
     676              :     GNUNET_PQ_result_spec_end
     677              :   };
     678            8 :   struct GNUNET_PQ_QueryParam params[] = {
     679            8 :     GNUNET_PQ_query_param_auto_from_type (rhc->reserve_pub),
     680            8 :     GNUNET_PQ_query_param_uint64 (&serial_id),
     681              :     GNUNET_PQ_query_param_end
     682              :   };
     683              : 
     684           31 :   while (0 < num_results--)
     685              :   {
     686              :     enum GNUNET_DB_QueryStatus qs;
     687           23 :     bool found = false;
     688              : 
     689           23 :     if (GNUNET_OK !=
     690           23 :         GNUNET_PQ_extract_result (result,
     691              :                                   rs,
     692              :                                   num_results))
     693              :     {
     694            0 :       GNUNET_break (0);
     695            0 :       rhc->failed = true;
     696            0 :       return;
     697              :     }
     698              : 
     699           23 :     for (unsigned int i = 0;
     700           62 :          NULL != work[i].cb;
     701           39 :          i++)
     702              :     {
     703          101 :       if (0 != strcmp (table_name,
     704           62 :                        work[i].table))
     705           39 :         continue;
     706           23 :       found = true;
     707           23 :       qs = GNUNET_PQ_eval_prepared_multi_select (rhc->pg->conn,
     708           23 :                                                  work[i].statement,
     709              :                                                  params,
     710           23 :                                                  work[i].cb,
     711              :                                                  rhc);
     712           23 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     713              :                   "Reserve %s had %d transactions at %llu in table %s\n",
     714              :                   TALER_B2S (rhc->reserve_pub),
     715              :                   (int) qs,
     716              :                   (unsigned long long) serial_id,
     717              :                   table_name);
     718           23 :       if (0 >= qs)
     719            0 :         rhc->failed = true;
     720           23 :       break;
     721              :     }
     722           23 :     if (! found)
     723              :     {
     724            0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     725              :                   "Reserve history includes unsupported table `%s`\n",
     726              :                   table_name);
     727            0 :       rhc->failed = true;
     728              :     }
     729           23 :     GNUNET_PQ_cleanup_result (rs);
     730           23 :     if (rhc->failed)
     731            0 :       break;
     732              :   }
     733              : }
     734              : 
     735              : 
     736              : enum GNUNET_DB_QueryStatus
     737            8 : TALER_EXCHANGEDB_get_reserve_history (
     738              :   struct TALER_EXCHANGEDB_PostgresContext *pg,
     739              :   const struct TALER_ReservePublicKeyP *reserve_pub,
     740              :   uint64_t start_off,
     741              :   uint64_t etag_in,
     742              :   uint64_t *etag_out,
     743              :   struct TALER_Amount *balance,
     744              :   struct TALER_EXCHANGEDB_ReserveHistory **rhp)
     745              : {
     746            8 :   struct ReserveHistoryContext rhc = {
     747              :     .pg = pg,
     748              :     .reserve_pub = reserve_pub
     749              :   };
     750            8 :   struct GNUNET_PQ_QueryParam params[] = {
     751            8 :     GNUNET_PQ_query_param_auto_from_type (reserve_pub),
     752              :     GNUNET_PQ_query_param_end
     753              :   };
     754            8 :   struct GNUNET_PQ_QueryParam lparams[] = {
     755            8 :     GNUNET_PQ_query_param_auto_from_type (reserve_pub),
     756            8 :     GNUNET_PQ_query_param_uint64 (&start_off),
     757              :     GNUNET_PQ_query_param_end
     758              :   };
     759              : 
     760            8 :   GNUNET_assert (GNUNET_OK ==
     761              :                  TALER_amount_set_zero (pg->currency,
     762              :                                         &rhc.balance_in));
     763            8 :   GNUNET_assert (GNUNET_OK ==
     764              :                  TALER_amount_set_zero (pg->currency,
     765              :                                         &rhc.balance_out));
     766              : 
     767            8 :   *rhp = NULL;
     768            8 :   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     769              :               "Getting transactions for reserve %s\n",
     770              :               TALER_B2S (reserve_pub));
     771            8 :   PREPARE (pg,
     772              :            "get_reserve_history_etag",
     773              :            "SELECT"
     774              :            " his.reserve_history_serial_id"
     775              :            ",r.current_balance"
     776              :            " FROM reserve_history his"
     777              :            " JOIN reserves r USING (reserve_pub)"
     778              :            " WHERE his.reserve_pub=$1"
     779              :            " ORDER BY reserve_history_serial_id DESC"
     780              :            " LIMIT 1;");
     781            8 :   PREPARE (pg,
     782              :            "get_reserve_history",
     783              :            "SELECT"
     784              :            " table_name"
     785              :            ",serial_id"
     786              :            ",reserve_history_serial_id"
     787              :            " FROM reserve_history"
     788              :            " WHERE reserve_pub=$1"
     789              :            "   AND reserve_history_serial_id > $2"
     790              :            " ORDER BY reserve_history_serial_id DESC;");
     791            8 :   PREPARE (pg,
     792              :            "reserves_in_get_transactions",
     793              :            "SELECT"
     794              :            " ri.wire_reference"
     795              :            ",ri.credit"
     796              :            ",ri.execution_date"
     797              :            ",wt.payto_uri AS sender_account_details"
     798              :            " FROM reserves_in ri"
     799              :            " JOIN wire_targets wt"
     800              :            "   ON (wire_source_h_payto = wire_target_h_payto)"
     801              :            " WHERE ri.reserve_pub=$1"
     802              :            "   AND ri.reserve_in_serial_id=$2;");
     803            8 :   PREPARE (pg,
     804              :            "get_withdraw_details",
     805              :            "SELECT"
     806              :            " planchets_h"
     807              :            ",amount_with_fee"
     808              :            ",reserve_sig"
     809              :            ",max_age"
     810              :            ",noreveal_index"
     811              :            ",selected_h"
     812              :            ",blinding_seed"
     813              :            ",denom_serials"
     814              :            ",ARRAY("
     815              :            "  SELECT denominations.denom_pub_hash FROM ("
     816              :            "    SELECT UNNEST(denom_serials) AS id,"
     817              :            "           generate_subscripts(denom_serials, 1) AS nr" /* for order */
     818              :            "  ) AS denoms"
     819              :            "  LEFT JOIN denominations ON denominations.denominations_serial=denoms.id"
     820              :            ") AS denom_pub_hashes"
     821              :            " FROM withdraw "
     822              :            " WHERE withdraw_id=$2"
     823              :            " AND reserve_pub=$1;");
     824            8 :   PREPARE (pg,
     825              :            "recoup_by_reserve",
     826              :            "SELECT"
     827              :            " rec.coin_pub"
     828              :            ",rec.coin_sig"
     829              :            ",rec.coin_blind"
     830              :            ",rec.amount"
     831              :            ",rec.recoup_timestamp"
     832              :            ",denom.denom_pub_hash"
     833              :            ",kc.denom_sig"
     834              :            " FROM recoup rec"
     835              :            " JOIN withdraw ro"
     836              :            "   USING (withdraw_id)"
     837              :            " JOIN reserves res"
     838              :            "   USING (reserve_pub)"
     839              :            " JOIN known_coins kc"
     840              :            "   USING (coin_pub)"
     841              :            " JOIN denominations denom"
     842              :            "   ON (denom.denominations_serial = kc.denominations_serial)"
     843              :            " WHERE rec.recoup_uuid=$2"
     844              :            "   AND res.reserve_pub=$1;");
     845            8 :   PREPARE (pg,
     846              :            "close_by_reserve",
     847              :            "SELECT"
     848              :            " rc.amount"
     849              :            ",rc.closing_fee"
     850              :            ",rc.execution_date"
     851              :            ",wt.payto_uri AS receiver_account"
     852              :            ",rc.wtid"
     853              :            " FROM reserves_close rc"
     854              :            " JOIN wire_targets wt"
     855              :            "   USING (wire_target_h_payto)"
     856              :            " WHERE reserve_pub=$1"
     857              :            "   AND close_uuid=$2;");
     858            8 :   PREPARE (pg,
     859              :            "merge_by_reserve",
     860              :            "SELECT"
     861              :            " pr.amount_with_fee"
     862              :            ",pr.balance"
     863              :            ",pr.purse_fee"
     864              :            ",pr.h_contract_terms"
     865              :            ",pr.merge_pub"
     866              :            ",am.reserve_sig"
     867              :            ",pm.purse_pub"
     868              :            ",pm.merge_timestamp"
     869              :            ",pr.purse_expiration"
     870              :            ",pr.age_limit"
     871              :            ",pr.flags"
     872              :            " FROM purse_decision pdes"
     873              :            "   JOIN purse_requests pr"
     874              :            "     ON (pr.purse_pub = pdes.purse_pub)"
     875              :            "   JOIN purse_merges pm"
     876              :            "     ON (pm.purse_pub = pdes.purse_pub)"
     877              :            "   JOIN account_merges am"
     878              :            "     ON (am.purse_pub = pm.purse_pub AND"
     879              :            "         am.reserve_pub = pm.reserve_pub)"
     880              :            " WHERE pdes.purse_decision_serial_id=$2"
     881              :            "  AND pm.reserve_pub=$1"
     882              :            "  AND COALESCE(pm.partner_serial_id,0)=0" /* must be local! */
     883              :            "  AND NOT pdes.refunded;");
     884            8 :   PREPARE (pg,
     885              :            "open_request_by_reserve",
     886              :            "SELECT"
     887              :            " reserve_payment"
     888              :            ",request_timestamp"
     889              :            ",expiration_date"
     890              :            ",requested_purse_limit"
     891              :            ",reserve_sig"
     892              :            " FROM reserves_open_requests"
     893              :            " WHERE reserve_pub=$1"
     894              :            "   AND open_request_uuid=$2;");
     895            8 :   PREPARE (pg,
     896              :            "close_request_by_reserve",
     897              :            "SELECT"
     898              :            " close_timestamp"
     899              :            ",payto_uri"
     900              :            ",reserve_sig"
     901              :            " FROM close_requests"
     902              :            " WHERE reserve_pub=$1"
     903              :            "   AND close_request_serial_id=$2;");
     904              : 
     905            8 :   for (unsigned int i = 0; i<RETRIES; i++)
     906              :   {
     907              :     enum GNUNET_DB_QueryStatus qs;
     908              :     uint64_t end;
     909            8 :     struct GNUNET_PQ_ResultSpec rs[] = {
     910            8 :       GNUNET_PQ_result_spec_uint64 ("reserve_history_serial_id",
     911              :                                     &end),
     912            8 :       TALER_PQ_RESULT_SPEC_AMOUNT ("current_balance",
     913              :                                    balance),
     914              :       GNUNET_PQ_result_spec_end
     915              :     };
     916              : 
     917            8 :     if (GNUNET_OK !=
     918            8 :         TALER_TALER_EXCHANGEDB_start_read_committed (pg,
     919              :                                                      "get-reserve-transactions")
     920              :         )
     921              :     {
     922            0 :       GNUNET_break (0);
     923            8 :       return GNUNET_DB_STATUS_HARD_ERROR;
     924              :     }
     925              :     /* First only check the last item, to see if
     926              :        we even need to iterate */
     927            8 :     qs = GNUNET_PQ_eval_prepared_singleton_select (
     928              :       pg->conn,
     929              :       "get_reserve_history_etag",
     930              :       params,
     931              :       rs);
     932            8 :     switch (qs)
     933              :     {
     934            0 :     case GNUNET_DB_STATUS_HARD_ERROR:
     935            0 :       TALER_EXCHANGEDB_rollback (pg);
     936            0 :       return qs;
     937            0 :     case GNUNET_DB_STATUS_SOFT_ERROR:
     938            0 :       TALER_EXCHANGEDB_rollback (pg);
     939            0 :       continue;
     940            0 :     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
     941            0 :       TALER_EXCHANGEDB_rollback (pg);
     942            0 :       return qs;
     943            8 :     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
     944            8 :       *etag_out = end;
     945            8 :       if (end == etag_in)
     946            0 :         return qs;
     947              :     }
     948              :     /* We indeed need to iterate over the history */
     949            8 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     950              :                 "Current ETag for reserve %s is %llu\n",
     951              :                 TALER_B2S (reserve_pub),
     952              :                 (unsigned long long) end);
     953              : 
     954            8 :     qs = GNUNET_PQ_eval_prepared_multi_select (
     955              :       pg->conn,
     956              :       "get_reserve_history",
     957              :       lparams,
     958              :       &handle_history_entry,
     959              :       &rhc);
     960            8 :     switch (qs)
     961              :     {
     962            0 :     case GNUNET_DB_STATUS_HARD_ERROR:
     963            0 :       TALER_EXCHANGEDB_rollback (pg);
     964            0 :       return qs;
     965            0 :     case GNUNET_DB_STATUS_SOFT_ERROR:
     966            0 :       TALER_EXCHANGEDB_rollback (pg);
     967            0 :       continue;
     968            8 :     default:
     969            8 :       break;
     970              :     }
     971            8 :     if (rhc.failed)
     972              :     {
     973            0 :       TALER_EXCHANGEDB_rollback (pg);
     974            0 :       TALER_EXCHANGEDB_free_reserve_history (rhc.rh);
     975            0 :       return GNUNET_DB_STATUS_SOFT_ERROR;
     976              :     }
     977            8 :     qs = TALER_EXCHANGEDB_commit (pg);
     978            8 :     switch (qs)
     979              :     {
     980            0 :     case GNUNET_DB_STATUS_HARD_ERROR:
     981            0 :       TALER_EXCHANGEDB_free_reserve_history (rhc.rh);
     982            0 :       return qs;
     983            0 :     case GNUNET_DB_STATUS_SOFT_ERROR:
     984            0 :       TALER_EXCHANGEDB_free_reserve_history (rhc.rh);
     985            0 :       rhc.rh = NULL;
     986            0 :       continue;
     987            8 :     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
     988              :     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
     989            8 :       *rhp = rhc.rh;
     990            8 :       return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
     991              :     }
     992              :   }
     993            0 :   return GNUNET_DB_STATUS_SOFT_ERROR;
     994              : }
        

Generated by: LCOV version 2.0-1