LCOV - code coverage report
Current view: top level - exchangedb - get_coin_transactions.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 62.6 % 385 241
Test Date: 2026-09-11 18:55:36 Functions: 72.7 % 11 8

            Line data    Source code
       1              : /*
       2              :    This file is part of TALER
       3              :    Copyright (C) 2022-2023 Taler Systems SA
       4              : 
       5              :    TALER is free software; you can redistribute it and/or modify it under the
       6              :    terms of the GNU General Public License as published by the Free Software
       7              :    Foundation; either version 3, or (at your option) any later version.
       8              : 
       9              :    TALER is distributed in the hope that it will be useful, but WITHOUT ANY
      10              :    WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
      11              :    A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
      12              : 
      13              :    You should have received a copy of the GNU General Public License along with
      14              :    TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
      15              :  */
      16              : /**
      17              :  * @file get_coin_transactions.c
      18              :  * @brief Low-level (statement-level) Postgres database access for the exchange
      19              :  * @author Christian Grothoff
      20              :  */
      21              : #include "taler/taler_error_codes.h"
      22              : #include "exchangedb_lib.h"
      23              : #include "taler/taler_pq_lib.h"
      24              : #include "exchange-database/get_coin_transactions.h"
      25              : #include "helper.h"
      26              : #include "exchange-database/start_read_committed.h"
      27              : #include "exchange-database/commit.h"
      28              : #include "exchange-database/rollback.h"
      29              : 
      30              : 
      31              : /**
      32              :  * How often do we re-try when encountering DB serialization issues?
      33              :  * (We are read-only, so can only happen due to concurrent insert,
      34              :  * which should be very rare.)
      35              :  */
      36              : #define RETRIES 3
      37              : 
      38              : /**
      39              :  * Closure for callbacks called from #TALER_EXCHANGEDB_get_coin_transactions()
      40              :  */
      41              : struct CoinHistoryContext
      42              : {
      43              :   /**
      44              :    * Head of the coin's history list.
      45              :    */
      46              :   struct TALER_EXCHANGEDB_TransactionList *head;
      47              : 
      48              :   /**
      49              :    * Public key of the coin we are building the history for.
      50              :    */
      51              :   const struct TALER_CoinSpendPublicKeyP *coin_pub;
      52              : 
      53              :   /**
      54              :    * Plugin context.
      55              :    */
      56              :   struct TALER_EXCHANGEDB_PostgresContext *pg;
      57              : 
      58              :   /**
      59              :    * Our current offset in the coin history.
      60              :    */
      61              :   uint64_t chid;
      62              : 
      63              :   /**
      64              :    * Set to 'true' if the transaction failed.
      65              :    */
      66              :   bool failed;
      67              : 
      68              : };
      69              : 
      70              : 
      71              : /**
      72              :  * Function to be called with the results of a SELECT statement
      73              :  * that has returned @a num_results results.
      74              :  *
      75              :  * @param cls closure of type `struct CoinHistoryContext`
      76              :  * @param result the postgres result
      77              :  * @param num_results the number of results in @a result
      78              :  */
      79              : static void
      80           10 : add_coin_deposit (void *cls,
      81              :                   PGresult *result,
      82              :                   unsigned int num_results)
      83              : {
      84           10 :   struct CoinHistoryContext *chc = cls;
      85           10 :   struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
      86              : 
      87           20 :   for (unsigned int i = 0; i < num_results; i++)
      88              :   {
      89              :     struct TALER_EXCHANGEDB_DepositListEntry *deposit;
      90              :     struct TALER_EXCHANGEDB_TransactionList *tl;
      91              :     uint64_t serial_id;
      92              : 
      93           10 :     deposit = GNUNET_new (struct TALER_EXCHANGEDB_DepositListEntry);
      94              :     {
      95           10 :       struct GNUNET_PQ_ResultSpec rs[] = {
      96           10 :         TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
      97              :                                      &deposit->amount_with_fee),
      98           10 :         TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
      99              :                                      &deposit->deposit_fee),
     100           10 :         GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
     101              :                                               &deposit->h_denom_pub),
     102           10 :         GNUNET_PQ_result_spec_allow_null (
     103           10 :           GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
     104              :                                                 &deposit->h_age_commitment),
     105              :           &deposit->no_age_commitment),
     106           10 :         GNUNET_PQ_result_spec_allow_null (
     107           10 :           GNUNET_PQ_result_spec_auto_from_type ("wallet_data_hash",
     108              :                                                 &deposit->wallet_data_hash),
     109              :           &deposit->no_wallet_data_hash),
     110           10 :         GNUNET_PQ_result_spec_timestamp ("wallet_timestamp",
     111              :                                          &deposit->timestamp),
     112           10 :         GNUNET_PQ_result_spec_timestamp ("refund_deadline",
     113              :                                          &deposit->refund_deadline),
     114           10 :         GNUNET_PQ_result_spec_timestamp ("wire_deadline",
     115              :                                          &deposit->wire_deadline),
     116           10 :         GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
     117              :                                               &deposit->merchant_pub),
     118           10 :         GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
     119              :                                               &deposit->h_contract_terms),
     120           10 :         GNUNET_PQ_result_spec_auto_from_type ("wire_salt",
     121              :                                               &deposit->wire_salt),
     122           10 :         GNUNET_PQ_result_spec_string ("payto_uri",
     123              :                                       &deposit->receiver_wire_account.full_payto
     124              :                                       ),
     125           10 :         GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
     126              :                                               &deposit->csig),
     127           10 :         GNUNET_PQ_result_spec_uint64 ("coin_deposit_serial_id",
     128              :                                       &serial_id),
     129           10 :         GNUNET_PQ_result_spec_auto_from_type ("done",
     130              :                                               &deposit->done),
     131              :         GNUNET_PQ_result_spec_end
     132              :       };
     133              : 
     134           10 :       if (GNUNET_OK !=
     135           10 :           GNUNET_PQ_extract_result (result,
     136              :                                     rs,
     137              :                                     i))
     138              :       {
     139            0 :         GNUNET_break (0);
     140            0 :         GNUNET_free (deposit);
     141            0 :         chc->failed = true;
     142            0 :         return;
     143              :       }
     144              :     }
     145           10 :     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
     146           10 :     tl->next = chc->head;
     147           10 :     tl->type = TALER_EXCHANGEDB_TT_DEPOSIT;
     148           10 :     tl->details.deposit = deposit;
     149           10 :     tl->serial_id = serial_id;
     150           10 :     tl->coin_history_id = chc->chid;
     151           10 :     chc->head = tl;
     152              :   }
     153              : }
     154              : 
     155              : 
     156              : /**
     157              :  * Function to be called with the results of a SELECT statement
     158              :  * that has returned @a num_results results.
     159              :  *
     160              :  * @param cls closure of type `struct CoinHistoryContext`
     161              :  * @param result the postgres result
     162              :  * @param num_results the number of results in @a result
     163              :  */
     164              : static void
     165            8 : add_coin_purse_deposit (void *cls,
     166              :                         PGresult *result,
     167              :                         unsigned int num_results)
     168              : {
     169            8 :   struct CoinHistoryContext *chc = cls;
     170            8 :   struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
     171              : 
     172           16 :   for (unsigned int i = 0; i < num_results; i++)
     173              :   {
     174              :     struct TALER_EXCHANGEDB_PurseDepositListEntry *deposit;
     175              :     struct TALER_EXCHANGEDB_TransactionList *tl;
     176              :     uint64_t serial_id;
     177              : 
     178            8 :     deposit = GNUNET_new (struct TALER_EXCHANGEDB_PurseDepositListEntry);
     179              :     {
     180              :       bool not_finished;
     181            8 :       struct GNUNET_PQ_ResultSpec rs[] = {
     182            8 :         TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
     183              :                                      &deposit->amount),
     184            8 :         TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
     185              :                                      &deposit->deposit_fee),
     186            8 :         GNUNET_PQ_result_spec_auto_from_type ("purse_pub",
     187              :                                               &deposit->purse_pub),
     188            8 :         GNUNET_PQ_result_spec_uint64 ("purse_deposit_serial_id",
     189              :                                       &serial_id),
     190            8 :         GNUNET_PQ_result_spec_allow_null (
     191              :           GNUNET_PQ_result_spec_string ("partner_base_url",
     192              :                                         &deposit->exchange_base_url),
     193              :           NULL),
     194            8 :         GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
     195              :                                               &deposit->coin_sig),
     196            8 :         GNUNET_PQ_result_spec_allow_null (
     197            8 :           GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
     198              :                                                 &deposit->h_age_commitment),
     199              :           &deposit->no_age_commitment),
     200            8 :         GNUNET_PQ_result_spec_allow_null (
     201              :           GNUNET_PQ_result_spec_bool ("refunded",
     202              :                                       &deposit->refunded),
     203              :           &not_finished),
     204            8 :         GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
     205              :                                               &deposit->h_denom_pub),
     206              :         GNUNET_PQ_result_spec_end
     207              :       };
     208              : 
     209            8 :       if (GNUNET_OK !=
     210            8 :           GNUNET_PQ_extract_result (result,
     211              :                                     rs,
     212              :                                     i))
     213              :       {
     214            0 :         GNUNET_break (0);
     215            0 :         GNUNET_free (deposit);
     216            0 :         chc->failed = true;
     217            0 :         return;
     218              :       }
     219            8 :       if (not_finished)
     220            6 :         deposit->refunded = false;
     221              :       /* double-check for all-zeros age commitment */
     222            8 :       if (! deposit->no_age_commitment)
     223              :         deposit->no_age_commitment
     224            0 :           = GNUNET_is_zero (&deposit->h_age_commitment);
     225              :     }
     226            8 :     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
     227            8 :     tl->next = chc->head;
     228            8 :     tl->type = TALER_EXCHANGEDB_TT_PURSE_DEPOSIT;
     229            8 :     tl->details.purse_deposit = deposit;
     230            8 :     tl->serial_id = serial_id;
     231            8 :     tl->coin_history_id = chc->chid;
     232            8 :     chc->head = tl;
     233              :   }
     234              : }
     235              : 
     236              : 
     237              : /**
     238              :  * Function to be called with the results of a SELECT statement
     239              :  * that has returned @a num_results results.
     240              :  *
     241              :  * @param cls closure of type `struct CoinHistoryContext`
     242              :  * @param result the postgres result
     243              :  * @param num_results the number of results in @a result
     244              :  */
     245              : static void
     246            5 : add_coin_melt (void *cls,
     247              :                PGresult *result,
     248              :                unsigned int num_results)
     249              : {
     250            5 :   struct CoinHistoryContext *chc = cls;
     251            5 :   struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
     252              : 
     253           10 :   for (unsigned int i = 0; i<num_results; i++)
     254              :   {
     255              :     struct TALER_EXCHANGEDB_MeltListEntry *melt;
     256              :     struct TALER_EXCHANGEDB_TransactionList *tl;
     257              :     uint64_t serial_id;
     258              : 
     259            5 :     melt = GNUNET_new (struct TALER_EXCHANGEDB_MeltListEntry);
     260              :     {
     261            5 :       struct TALER_DenominationHashP *denom_pub_hashes = NULL;
     262            5 :       struct GNUNET_PQ_ResultSpec rs[] = {
     263            5 :         GNUNET_PQ_result_spec_auto_from_type ("rc",
     264              :                                               &melt->rc),
     265              :         /* oldcoin_index not needed */
     266            5 :         GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
     267              :                                               &melt->h_denom_pub),
     268            5 :         GNUNET_PQ_result_spec_auto_from_type ("old_coin_sig",
     269              :                                               &melt->coin_sig),
     270            5 :         GNUNET_PQ_result_spec_auto_from_type ("refresh_seed",
     271              :                                               &melt->refresh_seed),
     272            5 :         TALER_PQ_result_spec_array_denom_hash (pg->conn,
     273              :                                                "denom_pub_hashes",
     274              :                                                &melt->num_coins,
     275              :                                                &denom_pub_hashes),
     276            5 :         GNUNET_PQ_result_spec_allow_null (
     277            5 :           GNUNET_PQ_result_spec_auto_from_type ("blinding_seed",
     278              :                                                 &melt->blinding_seed),
     279              :           &melt->no_blinding_seed),
     280            5 :         TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
     281              :                                      &melt->amount_with_fee),
     282            5 :         TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh",
     283              :                                      &melt->melt_fee),
     284            5 :         GNUNET_PQ_result_spec_allow_null (
     285            5 :           GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
     286              :                                                 &melt->h_age_commitment),
     287              :           &melt->no_age_commitment),
     288            5 :         GNUNET_PQ_result_spec_uint64 ("refresh_id",
     289              :                                       &serial_id),
     290              :         GNUNET_PQ_result_spec_end
     291              :       };
     292              : 
     293            5 :       if (GNUNET_OK !=
     294            5 :           GNUNET_PQ_extract_result (result,
     295              :                                     rs,
     296              :                                     i))
     297              :       {
     298            0 :         GNUNET_break (0);
     299            0 :         GNUNET_PQ_cleanup_result (rs);
     300            0 :         GNUNET_free (melt);
     301            0 :         chc->failed = true;
     302            0 :         return;
     303              :       }
     304            5 :       melt->denom_pub_hashes = denom_pub_hashes;
     305            5 :       denom_pub_hashes = NULL;
     306            5 :       GNUNET_PQ_cleanup_result (rs);
     307              :     }
     308            5 :     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
     309            5 :     tl->next = chc->head;
     310            5 :     tl->type = TALER_EXCHANGEDB_TT_MELT;
     311            5 :     tl->details.melt = melt;
     312            5 :     tl->serial_id = serial_id;
     313            5 :     tl->coin_history_id = chc->chid;
     314            5 :     chc->head = tl;
     315              :   }
     316              : }
     317              : 
     318              : 
     319              : /**
     320              :  * Function to be called with the results of a SELECT statement
     321              :  * that has returned @a num_results results.
     322              :  *
     323              :  * @param cls closure of type `struct CoinHistoryContext`
     324              :  * @param result the postgres result
     325              :  * @param num_results the number of results in @a result
     326              :  */
     327              : static void
     328            8 : add_coin_refund (void *cls,
     329              :                  PGresult *result,
     330              :                  unsigned int num_results)
     331              : {
     332            8 :   struct CoinHistoryContext *chc = cls;
     333            8 :   struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
     334              : 
     335           16 :   for (unsigned int i = 0; i<num_results; i++)
     336              :   {
     337              :     struct TALER_EXCHANGEDB_RefundListEntry *refund;
     338              :     struct TALER_EXCHANGEDB_TransactionList *tl;
     339              :     uint64_t serial_id;
     340              : 
     341            8 :     refund = GNUNET_new (struct TALER_EXCHANGEDB_RefundListEntry);
     342              :     {
     343            8 :       struct GNUNET_PQ_ResultSpec rs[] = {
     344            8 :         GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
     345              :                                               &refund->merchant_pub),
     346            8 :         GNUNET_PQ_result_spec_auto_from_type ("merchant_sig",
     347              :                                               &refund->merchant_sig),
     348            8 :         GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
     349              :                                               &refund->h_contract_terms),
     350            8 :         GNUNET_PQ_result_spec_uint64 ("rtransaction_id",
     351              :                                       &refund->rtransaction_id),
     352            8 :         TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
     353              :                                      &refund->refund_amount),
     354            8 :         TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
     355              :                                      &refund->refund_fee),
     356            8 :         GNUNET_PQ_result_spec_uint64 ("refund_serial_id",
     357              :                                       &serial_id),
     358              :         GNUNET_PQ_result_spec_end
     359              :       };
     360              : 
     361            8 :       if (GNUNET_OK !=
     362            8 :           GNUNET_PQ_extract_result (result,
     363              :                                     rs,
     364              :                                     i))
     365              :       {
     366            0 :         GNUNET_break (0);
     367            0 :         GNUNET_free (refund);
     368            0 :         chc->failed = true;
     369            0 :         return;
     370              :       }
     371              :     }
     372            8 :     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
     373            8 :     tl->next = chc->head;
     374            8 :     tl->type = TALER_EXCHANGEDB_TT_REFUND;
     375            8 :     tl->details.refund = refund;
     376            8 :     tl->serial_id = serial_id;
     377            8 :     tl->coin_history_id = chc->chid;
     378            8 :     chc->head = tl;
     379              :   }
     380              : }
     381              : 
     382              : 
     383              : /**
     384              :  * Function to be called with the results of a SELECT statement
     385              :  * that has returned @a num_results results.
     386              :  *
     387              :  * @param cls closure of type `struct CoinHistoryContext`
     388              :  * @param result the postgres result
     389              :  * @param num_results the number of results in @a result
     390              :  */
     391              : static void
     392            0 : add_coin_purse_decision (void *cls,
     393              :                          PGresult *result,
     394              :                          unsigned int num_results)
     395              : {
     396            0 :   struct CoinHistoryContext *chc = cls;
     397            0 :   struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
     398              : 
     399            0 :   for (unsigned int i = 0; i<num_results; i++)
     400              :   {
     401              :     struct TALER_EXCHANGEDB_PurseRefundListEntry *prefund;
     402              :     struct TALER_EXCHANGEDB_TransactionList *tl;
     403              :     uint64_t serial_id;
     404              : 
     405            0 :     prefund = GNUNET_new (struct TALER_EXCHANGEDB_PurseRefundListEntry);
     406              :     {
     407            0 :       struct GNUNET_PQ_ResultSpec rs[] = {
     408            0 :         GNUNET_PQ_result_spec_auto_from_type ("purse_pub",
     409              :                                               &prefund->purse_pub),
     410            0 :         TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
     411              :                                      &prefund->refund_amount),
     412            0 :         TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
     413              :                                      &prefund->refund_fee),
     414            0 :         GNUNET_PQ_result_spec_uint64 ("purse_decision_serial_id",
     415              :                                       &serial_id),
     416              :         GNUNET_PQ_result_spec_end
     417              :       };
     418              : 
     419            0 :       if (GNUNET_OK !=
     420            0 :           GNUNET_PQ_extract_result (result,
     421              :                                     rs,
     422              :                                     i))
     423              :       {
     424            0 :         GNUNET_break (0);
     425            0 :         GNUNET_free (prefund);
     426            0 :         chc->failed = true;
     427            0 :         return;
     428              :       }
     429              :     }
     430            0 :     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
     431            0 :     tl->next = chc->head;
     432            0 :     tl->type = TALER_EXCHANGEDB_TT_PURSE_REFUND;
     433            0 :     tl->details.purse_refund = prefund;
     434            0 :     tl->serial_id = serial_id;
     435            0 :     tl->coin_history_id = chc->chid;
     436            0 :     chc->head = tl;
     437              :   }
     438              : }
     439              : 
     440              : 
     441              : /**
     442              :  * Function to be called with the results of a SELECT statement
     443              :  * that has returned @a num_results results.
     444              :  *
     445              :  * @param cls closure of type `struct CoinHistoryContext`
     446              :  * @param result the postgres result
     447              :  * @param num_results the number of results in @a result
     448              :  */
     449              : static void
     450            0 : add_old_coin_recoup (void *cls,
     451              :                      PGresult *result,
     452              :                      unsigned int num_results)
     453              : {
     454            0 :   struct CoinHistoryContext *chc = cls;
     455            0 :   struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
     456              : 
     457            0 :   for (unsigned int i = 0; i<num_results; i++)
     458              :   {
     459              :     struct TALER_EXCHANGEDB_RecoupRefreshListEntry *recoup;
     460              :     struct TALER_EXCHANGEDB_TransactionList *tl;
     461              :     uint64_t serial_id;
     462              : 
     463            0 :     recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupRefreshListEntry);
     464              :     {
     465            0 :       struct GNUNET_PQ_ResultSpec rs[] = {
     466            0 :         GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
     467              :                                               &recoup->coin.coin_pub),
     468            0 :         GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
     469              :                                               &recoup->coin_sig),
     470            0 :         GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
     471              :                                               &recoup->coin_blind),
     472            0 :         TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
     473              :                                      &recoup->value),
     474            0 :         GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
     475              :                                          &recoup->timestamp),
     476            0 :         GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
     477              :                                               &recoup->coin.denom_pub_hash),
     478            0 :         TALER_PQ_result_spec_denom_sig ("denom_sig",
     479              :                                         &recoup->coin.denom_sig),
     480            0 :         GNUNET_PQ_result_spec_uint64 ("recoup_refresh_uuid",
     481              :                                       &serial_id),
     482              :         GNUNET_PQ_result_spec_end
     483              :       };
     484              : 
     485            0 :       if (GNUNET_OK !=
     486            0 :           GNUNET_PQ_extract_result (result,
     487              :                                     rs,
     488              :                                     i))
     489              :       {
     490            0 :         GNUNET_break (0);
     491            0 :         GNUNET_free (recoup);
     492            0 :         chc->failed = true;
     493            0 :         return;
     494              :       }
     495            0 :       recoup->old_coin_pub = *chc->coin_pub;
     496              :     }
     497            0 :     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
     498            0 :     tl->next = chc->head;
     499            0 :     tl->type = TALER_EXCHANGEDB_TT_RECOUP_REFRESH_RECEIVER;
     500            0 :     tl->details.old_coin_recoup = recoup;
     501            0 :     tl->serial_id = serial_id;
     502            0 :     tl->coin_history_id = chc->chid;
     503            0 :     chc->head = tl;
     504              :   }
     505              : }
     506              : 
     507              : 
     508              : /**
     509              :  * Function to be called with the results of a SELECT statement
     510              :  * that has returned @a num_results results.
     511              :  *
     512              :  * @param cls closure of type `struct CoinHistoryContext`
     513              :  * @param result the postgres result
     514              :  * @param num_results the number of results in @a result
     515              :  */
     516              : static void
     517            2 : add_coin_recoup (void *cls,
     518              :                  PGresult *result,
     519              :                  unsigned int num_results)
     520              : {
     521            2 :   struct CoinHistoryContext *chc = cls;
     522            2 :   struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
     523              : 
     524            4 :   for (unsigned int i = 0; i<num_results; i++)
     525              :   {
     526              :     struct TALER_EXCHANGEDB_RecoupListEntry *recoup;
     527              :     struct TALER_EXCHANGEDB_TransactionList *tl;
     528              :     uint64_t serial_id;
     529              : 
     530            2 :     recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupListEntry);
     531              :     {
     532            2 :       struct GNUNET_PQ_ResultSpec rs[] = {
     533            2 :         GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
     534              :                                               &recoup->reserve_pub),
     535            2 :         GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
     536              :                                               &recoup->coin_sig),
     537            2 :         GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
     538              :                                               &recoup->h_denom_pub),
     539            2 :         GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
     540              :                                               &recoup->coin_blind),
     541            2 :         TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
     542              :                                      &recoup->value),
     543            2 :         GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
     544              :                                          &recoup->timestamp),
     545            2 :         GNUNET_PQ_result_spec_uint64 ("recoup_uuid",
     546              :                                       &serial_id),
     547              :         GNUNET_PQ_result_spec_end
     548              :       };
     549              : 
     550            2 :       if (GNUNET_OK !=
     551            2 :           GNUNET_PQ_extract_result (result,
     552              :                                     rs,
     553              :                                     i))
     554              :       {
     555            0 :         GNUNET_break (0);
     556            0 :         GNUNET_free (recoup);
     557            0 :         chc->failed = true;
     558            0 :         return;
     559              :       }
     560              :     }
     561            2 :     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
     562            2 :     tl->next = chc->head;
     563            2 :     tl->type = TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW;
     564            2 :     tl->details.recoup = recoup;
     565            2 :     tl->serial_id = serial_id;
     566            2 :     tl->coin_history_id = chc->chid;
     567            2 :     chc->head = tl;
     568              :   }
     569              : }
     570              : 
     571              : 
     572              : /**
     573              :  * Function to be called with the results of a SELECT statement
     574              :  * that has returned @a num_results results.
     575              :  *
     576              :  * @param cls closure of type `struct CoinHistoryContext`
     577              :  * @param result the postgres result
     578              :  * @param num_results the number of results in @a result
     579              :  */
     580              : static void
     581            0 : add_coin_recoup_refresh (void *cls,
     582              :                          PGresult *result,
     583              :                          unsigned int num_results)
     584              : {
     585            0 :   struct CoinHistoryContext *chc = cls;
     586            0 :   struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
     587              : 
     588            0 :   for (unsigned int i = 0; i<num_results; i++)
     589              :   {
     590              :     struct TALER_EXCHANGEDB_RecoupRefreshListEntry *recoup;
     591              :     struct TALER_EXCHANGEDB_TransactionList *tl;
     592              :     uint64_t serial_id;
     593              : 
     594            0 :     recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupRefreshListEntry);
     595              :     {
     596            0 :       struct GNUNET_PQ_ResultSpec rs[] = {
     597            0 :         GNUNET_PQ_result_spec_auto_from_type ("old_coin_pub",
     598              :                                               &recoup->old_coin_pub),
     599            0 :         GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
     600              :                                               &recoup->coin_sig),
     601            0 :         GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
     602              :                                               &recoup->coin_blind),
     603            0 :         TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
     604              :                                      &recoup->value),
     605            0 :         GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
     606              :                                          &recoup->timestamp),
     607            0 :         GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
     608              :                                               &recoup->coin.denom_pub_hash),
     609            0 :         TALER_PQ_result_spec_denom_sig ("denom_sig",
     610              :                                         &recoup->coin.denom_sig),
     611            0 :         GNUNET_PQ_result_spec_uint64 ("recoup_refresh_uuid",
     612              :                                       &serial_id),
     613              :         GNUNET_PQ_result_spec_end
     614              :       };
     615              : 
     616            0 :       if (GNUNET_OK !=
     617            0 :           GNUNET_PQ_extract_result (result,
     618              :                                     rs,
     619              :                                     i))
     620              :       {
     621            0 :         GNUNET_break (0);
     622            0 :         GNUNET_free (recoup);
     623            0 :         chc->failed = true;
     624            0 :         return;
     625              :       }
     626            0 :       recoup->coin.coin_pub = *chc->coin_pub;
     627              :     }
     628            0 :     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
     629            0 :     tl->next = chc->head;
     630            0 :     tl->type = TALER_EXCHANGEDB_TT_RECOUP_REFRESH;
     631            0 :     tl->details.recoup_refresh = recoup;
     632            0 :     tl->serial_id = serial_id;
     633            0 :     tl->coin_history_id = chc->chid;
     634            0 :     chc->head = tl;
     635              :   }
     636              : }
     637              : 
     638              : 
     639              : /**
     640              :  * Function to be called with the results of a SELECT statement
     641              :  * that has returned @a num_results results.
     642              :  *
     643              :  * @param cls closure of type `struct CoinHistoryContext`
     644              :  * @param result the postgres result
     645              :  * @param num_results the number of results in @a result
     646              :  */
     647              : static void
     648            5 : add_coin_reserve_open (void *cls,
     649              :                        PGresult *result,
     650              :                        unsigned int num_results)
     651              : {
     652            5 :   struct CoinHistoryContext *chc = cls;
     653            5 :   struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
     654              : 
     655           10 :   for (unsigned int i = 0; i<num_results; i++)
     656              :   {
     657              :     struct TALER_EXCHANGEDB_ReserveOpenListEntry *role;
     658              :     struct TALER_EXCHANGEDB_TransactionList *tl;
     659              :     uint64_t serial_id;
     660              : 
     661            5 :     role = GNUNET_new (struct TALER_EXCHANGEDB_ReserveOpenListEntry);
     662              :     {
     663            5 :       struct GNUNET_PQ_ResultSpec rs[] = {
     664            5 :         GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
     665              :                                               &role->reserve_sig),
     666            5 :         GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
     667              :                                               &role->coin_sig),
     668            5 :         TALER_PQ_RESULT_SPEC_AMOUNT ("contribution",
     669              :                                      &role->coin_contribution),
     670            5 :         GNUNET_PQ_result_spec_allow_null (
     671            5 :           GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
     672              :                                                 &role->h_age_commitment),
     673              :           &role->no_age_commitment),
     674            5 :         GNUNET_PQ_result_spec_uint64 ("reserve_open_deposit_uuid",
     675              :                                       &serial_id),
     676              :         GNUNET_PQ_result_spec_end
     677              :       };
     678              : 
     679            5 :       if (GNUNET_OK !=
     680            5 :           GNUNET_PQ_extract_result (result,
     681              :                                     rs,
     682              :                                     i))
     683              :       {
     684            0 :         GNUNET_break (0);
     685            0 :         GNUNET_free (role);
     686            0 :         chc->failed = true;
     687            0 :         return;
     688              :       }
     689              :     }
     690            5 :     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
     691            5 :     tl->next = chc->head;
     692            5 :     tl->type = TALER_EXCHANGEDB_TT_RESERVE_OPEN;
     693            5 :     tl->details.reserve_open = role;
     694            5 :     tl->serial_id = serial_id;
     695            5 :     tl->coin_history_id = chc->chid;
     696            5 :     chc->head = tl;
     697              :   }
     698              : }
     699              : 
     700              : 
     701              : /**
     702              :  * Work we need to do.
     703              :  */
     704              : struct Work
     705              : {
     706              :   /**
     707              :    * Name of the table.
     708              :    */
     709              :   const char *table;
     710              : 
     711              :   /**
     712              :    * SQL prepared statement name.
     713              :    */
     714              :   const char *statement;
     715              : 
     716              :   /**
     717              :    * Function to call to handle the result(s).
     718              :    */
     719              :   GNUNET_PQ_PostgresResultHandler cb;
     720              : };
     721              : 
     722              : 
     723              : /**
     724              :  * We found a coin history entry. Lookup details
     725              :  * from the respective table and store in @a cls.
     726              :  *
     727              :  * @param[in,out] cls a `struct CoinHistoryContext`
     728              :  * @param result a coin history entry result set
     729              :  * @param num_results total number of results in @a results
     730              :  */
     731              : static void
     732           14 : handle_history_entry (void *cls,
     733              :                       PGresult *result,
     734              :                       unsigned int num_results)
     735              : {
     736           14 :   struct CoinHistoryContext *chc = cls;
     737           14 :   struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
     738              :   static const struct Work work[] = {
     739              :     [TALER_EXCHANGEDB_TT_DEPOSIT] =
     740              :     { "coin_deposits",
     741              :       "get_coin_transactions_deposit_with_coin_pub",
     742              :       &add_coin_deposit },
     743              :     [TALER_EXCHANGEDB_TT_MELT] =
     744              :     { "refresh",
     745              :       "get_coin_transactions_refresh_by_coin",
     746              :       &add_coin_melt },
     747              :     [TALER_EXCHANGEDB_TT_PURSE_DEPOSIT] =
     748              :     { "purse_deposits",
     749              :       "get_coin_transactions_purse_deposit_by_coin_pub",
     750              :       &add_coin_purse_deposit },
     751              :     [TALER_EXCHANGEDB_TT_PURSE_REFUND] =
     752              :     { "purse_decision",
     753              :       "get_coin_transactions_purse_decision_by_coin_pub",
     754              :       &add_coin_purse_decision },
     755              :     [TALER_EXCHANGEDB_TT_REFUND] =
     756              :     { "refunds",
     757              :       "get_coin_transactions_refunds_by_coin",
     758              :       &add_coin_refund },
     759              :     [TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW] =
     760              :     { "recoup",
     761              :       "get_coin_transactions_recoup_by_coin",
     762              :       &add_coin_recoup },
     763              :     [TALER_EXCHANGEDB_TT_RECOUP_REFRESH] =
     764              :     { "recoup_refresh::NEW",
     765              :       "get_coin_transactions_recoup_by_refreshed_coin",
     766              :       &add_coin_recoup_refresh },
     767              :     [TALER_EXCHANGEDB_TT_RECOUP_REFRESH_RECEIVER] =
     768              :     { "recoup_refresh::OLD",
     769              :       "get_coin_transactions_recoup_by_old_coin",
     770              :       &add_old_coin_recoup },
     771              :     [TALER_EXCHANGEDB_TT_RESERVE_OPEN] =
     772              :     { "reserves_open_deposits",
     773              :       "get_coin_transactions_reserve_open_by_coin",
     774              :       &add_coin_reserve_open },
     775              :     { NULL, NULL, NULL }
     776              :   };
     777              :   char *table_name;
     778              :   uint64_t serial_id;
     779           14 :   struct GNUNET_PQ_ResultSpec rs[] = {
     780           14 :     GNUNET_PQ_result_spec_string ("table_name",
     781              :                                   &table_name),
     782           14 :     GNUNET_PQ_result_spec_uint64 ("serial_id",
     783              :                                   &serial_id),
     784           14 :     GNUNET_PQ_result_spec_uint64 ("coin_history_serial_id",
     785              :                                   &chc->chid),
     786              :     GNUNET_PQ_result_spec_end
     787              :   };
     788           14 :   struct GNUNET_PQ_QueryParam params[] = {
     789           14 :     GNUNET_PQ_query_param_auto_from_type (chc->coin_pub),
     790           14 :     GNUNET_PQ_query_param_uint64 (&serial_id),
     791              :     GNUNET_PQ_query_param_end
     792              :   };
     793              : 
     794           52 :   for (unsigned int i = 0; i<num_results; i++)
     795              :   {
     796              :     enum GNUNET_DB_QueryStatus qs;
     797           38 :     bool found = false;
     798              : 
     799           38 :     if (GNUNET_OK !=
     800           38 :         GNUNET_PQ_extract_result (result,
     801              :                                   rs,
     802              :                                   i))
     803              :     {
     804            0 :       GNUNET_break (0);
     805            0 :       chc->failed = true;
     806            0 :       return;
     807              :     }
     808              : 
     809           38 :     for (unsigned int s = 0;
     810          155 :          NULL != work[s].statement;
     811          117 :          s++)
     812              :     {
     813          272 :       if (0 != strcmp (table_name,
     814          155 :                        work[s].table))
     815          117 :         continue;
     816           38 :       found = true;
     817           38 :       qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
     818           38 :                                                  work[s].statement,
     819              :                                                  params,
     820           38 :                                                  work[s].cb,
     821              :                                                  chc);
     822           38 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     823              :                   "Coin %s had %d transactions at %llu in table %s\n",
     824              :                   TALER_B2S (chc->coin_pub),
     825              :                   (int) qs,
     826              :                   (unsigned long long) serial_id,
     827              :                   table_name);
     828           38 :       if (0 > qs)
     829            0 :         chc->failed = true;
     830           38 :       break;
     831              :     }
     832           38 :     if (! found)
     833              :     {
     834            0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     835              :                   "Coin history includes unsupported table `%s`\n",
     836              :                   table_name);
     837            0 :       chc->failed = true;
     838              :     }
     839           38 :     GNUNET_PQ_cleanup_result (rs);
     840           38 :     if (chc->failed)
     841            0 :       break;
     842              :   }
     843              : }
     844              : 
     845              : 
     846              : enum GNUNET_DB_QueryStatus
     847           17 : TALER_EXCHANGEDB_get_coin_transactions (
     848              :   struct TALER_EXCHANGEDB_PostgresContext *pg,
     849              :   bool begin_transaction,
     850              :   const struct TALER_CoinSpendPublicKeyP *coin_pub,
     851              :   uint64_t start_off,
     852              :   uint64_t etag_in,
     853              :   uint64_t *etag_out,
     854              :   struct TALER_Amount *balance,
     855              :   struct TALER_DenominationHashP *h_denom_pub,
     856              :   struct TALER_EXCHANGEDB_TransactionList **tlp)
     857              : {
     858           17 :   struct GNUNET_PQ_QueryParam params[] = {
     859           17 :     GNUNET_PQ_query_param_auto_from_type (coin_pub),
     860              :     GNUNET_PQ_query_param_end
     861              :   };
     862           17 :   struct GNUNET_PQ_QueryParam lparams[] = {
     863           17 :     GNUNET_PQ_query_param_auto_from_type (coin_pub),
     864           17 :     GNUNET_PQ_query_param_uint64 (&start_off),
     865              :     GNUNET_PQ_query_param_end
     866              :   };
     867           17 :   struct CoinHistoryContext chc = {
     868              :     .head = NULL,
     869              :     .coin_pub = coin_pub,
     870              :     .pg = pg
     871              :   };
     872              : 
     873           17 :   *tlp = NULL;
     874           17 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     875              :               "Getting transactions for coin %s\n",
     876              :               TALER_B2S (coin_pub));
     877           17 :   PREPARE (pg,
     878              :            "get_coin_transactions_history_etag_balance",
     879              :            "SELECT"
     880              :            " ch.coin_history_serial_id"
     881              :            ",kc.remaining"
     882              :            ",denom.denom_pub_hash"
     883              :            " FROM coin_history ch"
     884              :            " JOIN known_coins kc"
     885              :            "   USING (coin_pub)"
     886              :            " JOIN denominations denom"
     887              :            "   USING (denominations_serial)"
     888              :            " WHERE coin_pub=$1"
     889              :            " ORDER BY coin_history_serial_id DESC"
     890              :            " LIMIT 1;");
     891           17 :   PREPARE (pg,
     892              :            "get_coin_transactions_history",
     893              :            "SELECT"
     894              :            " table_name"
     895              :            ",serial_id"
     896              :            ",coin_history_serial_id"
     897              :            " FROM coin_history"
     898              :            " WHERE coin_pub=$1"
     899              :            "   AND coin_history_serial_id > $2"
     900              :            " ORDER BY coin_history_serial_id DESC;");
     901           17 :   PREPARE (pg,
     902              :            "get_coin_transactions_deposit_with_coin_pub",
     903              :            "SELECT"
     904              :            " cdep.amount_with_fee"
     905              :            ",denoms.fee_deposit"
     906              :            ",denoms.denom_pub_hash"
     907              :            ",kc.age_commitment_hash"
     908              :            ",bdep.wallet_timestamp"
     909              :            ",bdep.refund_deadline"
     910              :            ",bdep.wire_deadline"
     911              :            ",bdep.merchant_pub"
     912              :            ",bdep.h_contract_terms"
     913              :            ",bdep.wallet_data_hash"
     914              :            ",bdep.wire_salt"
     915              :            ",wt.payto_uri"
     916              :            ",cdep.coin_sig"
     917              :            ",cdep.coin_deposit_serial_id"
     918              :            ",bdep.done"
     919              :            " FROM coin_deposits cdep"
     920              :            " JOIN batch_deposits bdep"
     921              :            "   USING (batch_deposit_serial_id)"
     922              :            " JOIN wire_targets wt"
     923              :            "   USING (wire_target_h_payto)"
     924              :            " JOIN known_coins kc"
     925              :            "   ON (kc.coin_pub = cdep.coin_pub)"
     926              :            " JOIN denominations denoms"
     927              :            "   USING (denominations_serial)"
     928              :            " WHERE cdep.coin_pub=$1"
     929              :            "   AND cdep.coin_deposit_serial_id=$2;");
     930           17 :   PREPARE (pg,
     931              :            "get_coin_transactions_refresh_by_coin",
     932              :            "SELECT"
     933              :            " rc"
     934              :            ",refresh_seed"
     935              :            ",blinding_seed"
     936              :            ",old_coin_sig"
     937              :            ",amount_with_fee"
     938              :            ",denoms.denom_pub_hash"
     939              :            ",denoms.fee_refresh"
     940              :            ",kc.age_commitment_hash"
     941              :            ",refresh_id"
     942              :            ",ARRAY("
     943              :            " SELECT fresh_denoms.denom_pub_hash"
     944              :            " FROM unnest(refresh.denom_serials) WITH ORDINALITY"
     945              :            " AS fresh(denom_serial, coin_index)"
     946              :            " LEFT JOIN denominations fresh_denoms"
     947              :            " ON fresh_denoms.denominations_serial=fresh.denom_serial"
     948              :            " ORDER BY fresh.coin_index"
     949              :            ") AS denom_pub_hashes"
     950              :            " FROM refresh"
     951              :            " JOIN known_coins kc"
     952              :            "   ON (refresh.old_coin_pub = kc.coin_pub)"
     953              :            " JOIN denominations denoms"
     954              :            "   USING (denominations_serial)"
     955              :            " WHERE old_coin_pub=$1"
     956              :            "   AND refresh_id=$2;");
     957           17 :   PREPARE (pg,
     958              :            "get_coin_transactions_purse_deposit_by_coin_pub",
     959              :            "SELECT"
     960              :            " partner_base_url"
     961              :            ",pd.amount_with_fee"
     962              :            ",denoms.fee_deposit"
     963              :            ",denoms.denom_pub_hash"
     964              :            ",pd.purse_pub"
     965              :            ",kc.age_commitment_hash"
     966              :            ",pd.coin_sig"
     967              :            ",pd.purse_deposit_serial_id"
     968              :            ",pdes.refunded"
     969              :            " FROM purse_deposits pd"
     970              :            " LEFT JOIN partners"
     971              :            "   USING (partner_serial_id)"
     972              :            " JOIN purse_requests pr"
     973              :            "   USING (purse_pub)"
     974              :            " LEFT JOIN purse_decision pdes"
     975              :            "   USING (purse_pub)"
     976              :            " JOIN known_coins kc"
     977              :            "   ON (pd.coin_pub = kc.coin_pub)"
     978              :            " JOIN denominations denoms"
     979              :            "   USING (denominations_serial)"
     980              :            " WHERE pd.purse_deposit_serial_id=$2"
     981              :            "   AND pd.coin_pub=$1;");
     982           17 :   PREPARE (pg,
     983              :            "get_coin_transactions_purse_decision_by_coin_pub",
     984              :            "SELECT"
     985              :            " pdes.purse_pub"
     986              :            ",pd.amount_with_fee"
     987              :            ",denom.fee_refund"
     988              :            ",pdes.purse_decision_serial_id"
     989              :            " FROM purse_decision pdes"
     990              :            " JOIN purse_deposits pd"
     991              :            "   USING (purse_pub)"
     992              :            " JOIN known_coins kc"
     993              :            "   ON (pd.coin_pub = kc.coin_pub)"
     994              :            " JOIN denominations denom"
     995              :            "   USING (denominations_serial)"
     996              :            " WHERE pd.coin_pub=$1"
     997              :            "   AND pdes.purse_decision_serial_id=$2"
     998              :            "   AND pdes.refunded;");
     999           17 :   PREPARE (pg,
    1000              :            "get_coin_transactions_refunds_by_coin",
    1001              :            "SELECT"
    1002              :            " bdep.merchant_pub"
    1003              :            ",ref.merchant_sig"
    1004              :            ",bdep.h_contract_terms"
    1005              :            ",ref.rtransaction_id"
    1006              :            ",ref.amount_with_fee"
    1007              :            ",denom.fee_refund"
    1008              :            ",ref.refund_serial_id"
    1009              :            " FROM refunds ref"
    1010              :            " JOIN coin_deposits cdep"
    1011              :            "   ON (ref.coin_pub = cdep.coin_pub AND ref.batch_deposit_serial_id = cdep.batch_deposit_serial_id)"
    1012              :            " JOIN batch_deposits bdep"
    1013              :            "   ON (ref.batch_deposit_serial_id = bdep.batch_deposit_serial_id)"
    1014              :            " JOIN known_coins kc"
    1015              :            "   ON (ref.coin_pub = kc.coin_pub)"
    1016              :            " JOIN denominations denom"
    1017              :            "   USING (denominations_serial)"
    1018              :            " WHERE ref.coin_pub=$1"
    1019              :            "   AND ref.refund_serial_id=$2;");
    1020           17 :   PREPARE (pg,
    1021              :            "get_coin_transactions_recoup_by_old_coin",
    1022              :            "SELECT"
    1023              :            " coins.coin_pub"
    1024              :            ",rr.coin_sig"
    1025              :            ",rr.coin_blind"
    1026              :            ",rr.amount"
    1027              :            ",rr.recoup_timestamp"
    1028              :            ",denoms.denom_pub_hash"
    1029              :            ",coins.denom_sig"
    1030              :            ",rr.recoup_refresh_uuid"
    1031              :            " FROM recoup_refresh rr"
    1032              :            " JOIN known_coins coins"
    1033              :            "   USING (coin_pub)"
    1034              :            " JOIN denominations denoms"
    1035              :            "   USING (denominations_serial)"
    1036              :            " WHERE recoup_refresh_uuid=$2"
    1037              :            "   AND refresh_id IN"
    1038              :            "   (SELECT refresh_id"
    1039              :            "    FROM refresh"
    1040              :            "    WHERE refresh.old_coin_pub=$1);");
    1041           17 :   PREPARE (pg,
    1042              :            "get_coin_transactions_recoup_by_coin",
    1043              :            "SELECT"
    1044              :            " res.reserve_pub"
    1045              :            ",denoms.denom_pub_hash"
    1046              :            ",rcp.coin_sig"
    1047              :            ",rcp.coin_blind"
    1048              :            ",rcp.amount"
    1049              :            ",rcp.recoup_timestamp"
    1050              :            ",rcp.recoup_uuid"
    1051              :            " FROM recoup rcp"
    1052              :            " JOIN withdraw ro"
    1053              :            "   USING (withdraw_id)"
    1054              :            " JOIN reserves res"
    1055              :            "   USING (reserve_pub)"
    1056              :            " JOIN known_coins coins"
    1057              :            "   USING (coin_pub)"
    1058              :            " JOIN denominations denoms"
    1059              :            "   ON (denoms.denominations_serial = coins.denominations_serial)"
    1060              :            " WHERE rcp.recoup_uuid=$2"
    1061              :            "   AND coins.coin_pub=$1;");
    1062              :   /* Used to obtain recoup transactions
    1063              :      for a refreshed coin */
    1064           17 :   PREPARE (pg,
    1065              :            "get_coin_transactions_recoup_by_refreshed_coin",
    1066              :            "SELECT"
    1067              :            " old_coins.coin_pub AS old_coin_pub"
    1068              :            ",rr.coin_sig"
    1069              :            ",rr.coin_blind"
    1070              :            ",rr.amount"
    1071              :            ",rr.recoup_timestamp"
    1072              :            ",denoms.denom_pub_hash"
    1073              :            ",coins.denom_sig"
    1074              :            ",recoup_refresh_uuid"
    1075              :            " FROM recoup_refresh rr"
    1076              :            "    JOIN refresh rfc"
    1077              :            "      ON (rr.refresh_id = rfc.refresh_id)"
    1078              :            "    JOIN known_coins old_coins"
    1079              :            "      ON (rfc.old_coin_pub = old_coins.coin_pub)"
    1080              :            "    JOIN known_coins coins"
    1081              :            "      ON (rr.coin_pub = coins.coin_pub)"
    1082              :            "    JOIN denominations denoms"
    1083              :            "      ON (denoms.denominations_serial = coins.denominations_serial)"
    1084              :            " WHERE rr.recoup_refresh_uuid=$2"
    1085              :            "   AND coins.coin_pub=$1;");
    1086           17 :   PREPARE (pg,
    1087              :            "get_coin_transactions_reserve_open_by_coin",
    1088              :            "SELECT"
    1089              :            " rod.reserve_open_deposit_uuid"
    1090              :            ",rod.coin_sig"
    1091              :            ",rod.reserve_sig"
    1092              :            ",rod.contribution"
    1093              :            ",kc.age_commitment_hash"
    1094              :            " FROM reserves_open_deposits rod"
    1095              :            " JOIN known_coins kc"
    1096              :            "   ON (rod.coin_pub = kc.coin_pub)"
    1097              :            " WHERE rod.coin_pub=$1"
    1098              :            "   AND rod.reserve_open_deposit_uuid=$2;");
    1099           17 :   for (unsigned int i = 0; i<RETRIES; i++)
    1100              :   {
    1101              :     enum GNUNET_DB_QueryStatus qs;
    1102              :     uint64_t end;
    1103           17 :     struct GNUNET_PQ_ResultSpec rs[] = {
    1104           17 :       GNUNET_PQ_result_spec_uint64 ("coin_history_serial_id",
    1105              :                                     &end),
    1106           17 :       GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
    1107              :                                             h_denom_pub),
    1108           17 :       TALER_PQ_RESULT_SPEC_AMOUNT ("remaining",
    1109              :                                    balance),
    1110              :       GNUNET_PQ_result_spec_end
    1111              :     };
    1112              : 
    1113           17 :     if (begin_transaction)
    1114              :     {
    1115           16 :       if (GNUNET_OK !=
    1116           16 :           TALER_EXCHANGEDB_start_read_committed (pg,
    1117              :                                                  "get-coin-transactions"))
    1118              :       {
    1119            0 :         GNUNET_break (0);
    1120           17 :         return GNUNET_DB_STATUS_HARD_ERROR;
    1121              :       }
    1122              :     }
    1123              :     /* First only check the last item, to see if
    1124              :        we even need to iterate */
    1125           17 :     qs = GNUNET_PQ_eval_prepared_singleton_select (
    1126              :       pg->conn,
    1127              :       "get_coin_transactions_history_etag_balance",
    1128              :       params,
    1129              :       rs);
    1130           17 :     switch (qs)
    1131              :     {
    1132            0 :     case GNUNET_DB_STATUS_HARD_ERROR:
    1133            0 :       if (begin_transaction)
    1134            0 :         TALER_EXCHANGEDB_rollback (pg);
    1135            0 :       return qs;
    1136            0 :     case GNUNET_DB_STATUS_SOFT_ERROR:
    1137            0 :       if (begin_transaction)
    1138            0 :         TALER_EXCHANGEDB_rollback (pg);
    1139            0 :       continue;
    1140            2 :     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    1141            2 :       if (begin_transaction)
    1142            2 :         TALER_EXCHANGEDB_rollback (pg);
    1143            2 :       return qs;
    1144           15 :     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    1145           15 :       *etag_out = end;
    1146           15 :       if (end == etag_in)
    1147              :       {
    1148              :         /* client is up-to-date, nothing more to do */
    1149            1 :         if (begin_transaction)
    1150            1 :           TALER_EXCHANGEDB_rollback (pg);
    1151            1 :         return qs;
    1152              :       }
    1153              :     }
    1154              :     /* We indeed need to iterate over the history */
    1155           14 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1156              :                 "Current ETag for coin %s is %llu\n",
    1157              :                 TALER_B2S (coin_pub),
    1158              :                 (unsigned long long) end);
    1159              : 
    1160           14 :     qs = GNUNET_PQ_eval_prepared_multi_select (
    1161              :       pg->conn,
    1162              :       "get_coin_transactions_history",
    1163              :       lparams,
    1164              :       &handle_history_entry,
    1165              :       &chc);
    1166           14 :     switch (qs)
    1167              :     {
    1168            0 :     case GNUNET_DB_STATUS_HARD_ERROR:
    1169            0 :       if (begin_transaction)
    1170            0 :         TALER_EXCHANGEDB_rollback (pg);
    1171            0 :       return qs;
    1172            0 :     case GNUNET_DB_STATUS_SOFT_ERROR:
    1173            0 :       if (begin_transaction)
    1174            0 :         TALER_EXCHANGEDB_rollback (pg);
    1175            0 :       TALER_EXCHANGEDB_free_coin_transaction_list (chc.head);
    1176            0 :       chc.head = NULL;
    1177            0 :       continue;
    1178           14 :     default:
    1179           14 :       break;
    1180              :     }
    1181           14 :     if (chc.failed)
    1182              :     {
    1183            0 :       if (begin_transaction)
    1184            0 :         TALER_EXCHANGEDB_rollback (pg);
    1185            0 :       TALER_EXCHANGEDB_free_coin_transaction_list (chc.head);
    1186            0 :       return GNUNET_DB_STATUS_SOFT_ERROR;
    1187              :     }
    1188           14 :     if (! begin_transaction)
    1189              :     {
    1190            1 :       *tlp = chc.head;
    1191            1 :       return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
    1192              :     }
    1193           13 :     qs = TALER_EXCHANGEDB_commit (pg);
    1194           13 :     switch (qs)
    1195              :     {
    1196            0 :     case GNUNET_DB_STATUS_HARD_ERROR:
    1197            0 :       TALER_EXCHANGEDB_free_coin_transaction_list (chc.head);
    1198            0 :       chc.head = NULL;
    1199            0 :       return qs;
    1200            0 :     case GNUNET_DB_STATUS_SOFT_ERROR:
    1201            0 :       TALER_EXCHANGEDB_free_coin_transaction_list (chc.head);
    1202            0 :       chc.head = NULL;
    1203            0 :       continue;
    1204           13 :     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    1205              :     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    1206           13 :       *tlp = chc.head;
    1207           13 :       return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
    1208              :     }
    1209              :   }
    1210            0 :   return GNUNET_DB_STATUS_SOFT_ERROR;
    1211              : }
        

Generated by: LCOV version 2.0-1