LCOV - code coverage report
Current view: top level - exchangedb - get_reserve_history.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 58.6 % 307 180
Test Date: 2026-09-11 18:55:36 Functions: 70.0 % 10 7

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

Generated by: LCOV version 2.0-1