LCOV - code coverage report
Current view: top level - exchangedb - pg_get_reserve_history.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 164 310 52.9 %
Date: 2025-06-05 21:03:14 Functions: 6 10 60.0 %

          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 "platform.h"
      22             : #include "taler_error_codes.h"
      23             : #include "taler_dbevents.h"
      24             : #include "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 1.16