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

Generated by: LCOV version 2.0-1