LCOV - code coverage report
Current view: top level - exchangedb - get_coin_transactions.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 75.4 % 391 295
Test Date: 2026-10-10 21:00:14 Functions: 90.9 % 11 10

            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           75 : add_coin_deposit (void *cls,
      81              :                   PGresult *result,
      82              :                   unsigned int num_results)
      83              : {
      84           75 :   struct CoinHistoryContext *chc = cls;
      85           75 :   struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
      86              : 
      87          150 :   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           75 :     deposit = GNUNET_new (struct TALER_EXCHANGEDB_DepositListEntry);
      94              :     {
      95           75 :       struct GNUNET_PQ_ResultSpec rs[] = {
      96           75 :         TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
      97              :                                      &deposit->amount_with_fee),
      98           75 :         TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
      99              :                                      &deposit->deposit_fee),
     100           75 :         GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
     101              :                                               &deposit->h_denom_pub),
     102           75 :         GNUNET_PQ_result_spec_allow_null (
     103           75 :           GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
     104              :                                                 &deposit->h_age_commitment),
     105              :           &deposit->no_age_commitment),
     106           75 :         GNUNET_PQ_result_spec_allow_null (
     107           75 :           GNUNET_PQ_result_spec_auto_from_type ("wallet_data_hash",
     108              :                                                 &deposit->wallet_data_hash),
     109              :           &deposit->no_wallet_data_hash),
     110           75 :         GNUNET_PQ_result_spec_timestamp ("wallet_timestamp",
     111              :                                          &deposit->timestamp),
     112           75 :         GNUNET_PQ_result_spec_timestamp ("refund_deadline",
     113              :                                          &deposit->refund_deadline),
     114           75 :         GNUNET_PQ_result_spec_timestamp ("wire_deadline",
     115              :                                          &deposit->wire_deadline),
     116           75 :         GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
     117              :                                               &deposit->merchant_pub),
     118           75 :         GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
     119              :                                               &deposit->h_contract_terms),
     120           75 :         GNUNET_PQ_result_spec_auto_from_type ("wire_salt",
     121              :                                               &deposit->wire_salt),
     122           75 :         GNUNET_PQ_result_spec_string ("payto_uri",
     123              :                                       &deposit->receiver_wire_account.full_payto
     124              :                                       ),
     125           75 :         GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
     126              :                                               &deposit->csig),
     127           75 :         GNUNET_PQ_result_spec_uint64 ("coin_deposit_serial_id",
     128              :                                       &serial_id),
     129           75 :         GNUNET_PQ_result_spec_auto_from_type ("done",
     130              :                                               &deposit->done),
     131              :         GNUNET_PQ_result_spec_end
     132              :       };
     133              : 
     134           75 :       if (GNUNET_OK !=
     135           75 :           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           75 :     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
     146           75 :     tl->next = chc->head;
     147           75 :     tl->type = TALER_EXCHANGEDB_TT_DEPOSIT;
     148           75 :     tl->details.deposit = deposit;
     149           75 :     tl->serial_id = serial_id;
     150           75 :     tl->coin_history_id = chc->chid;
     151           75 :     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            9 : add_coin_purse_deposit (void *cls,
     166              :                         PGresult *result,
     167              :                         unsigned int num_results)
     168              : {
     169            9 :   struct CoinHistoryContext *chc = cls;
     170            9 :   struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
     171              : 
     172           18 :   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            9 :     deposit = GNUNET_new (struct TALER_EXCHANGEDB_PurseDepositListEntry);
     179              :     {
     180              :       bool not_finished;
     181            9 :       struct GNUNET_PQ_ResultSpec rs[] = {
     182            9 :         TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
     183              :                                      &deposit->amount),
     184            9 :         TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
     185              :                                      &deposit->deposit_fee),
     186            9 :         GNUNET_PQ_result_spec_auto_from_type ("purse_pub",
     187              :                                               &deposit->purse_pub),
     188            9 :         GNUNET_PQ_result_spec_uint64 ("purse_deposit_serial_id",
     189              :                                       &serial_id),
     190            9 :         GNUNET_PQ_result_spec_allow_null (
     191              :           GNUNET_PQ_result_spec_string ("partner_base_url",
     192              :                                         &deposit->exchange_base_url),
     193              :           NULL),
     194            9 :         GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
     195              :                                               &deposit->coin_sig),
     196            9 :         GNUNET_PQ_result_spec_allow_null (
     197            9 :           GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
     198              :                                                 &deposit->h_age_commitment),
     199              :           &deposit->no_age_commitment),
     200            9 :         GNUNET_PQ_result_spec_allow_null (
     201              :           GNUNET_PQ_result_spec_bool ("refunded",
     202              :                                       &deposit->refunded),
     203              :           &not_finished),
     204            9 :         GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
     205              :                                               &deposit->h_denom_pub),
     206              :         GNUNET_PQ_result_spec_end
     207              :       };
     208              : 
     209            9 :       if (GNUNET_OK !=
     210            9 :           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            9 :       if (not_finished)
     220            7 :         deposit->refunded = false;
     221              :       /* double-check for all-zeros age commitment */
     222            9 :       if (! deposit->no_age_commitment)
     223              :         deposit->no_age_commitment
     224            0 :           = GNUNET_is_zero (&deposit->h_age_commitment);
     225              :     }
     226            9 :     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
     227            9 :     tl->next = chc->head;
     228            9 :     tl->type = TALER_EXCHANGEDB_TT_PURSE_DEPOSIT;
     229            9 :     tl->details.purse_deposit = deposit;
     230            9 :     tl->serial_id = serial_id;
     231            9 :     tl->coin_history_id = chc->chid;
     232            9 :     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           10 : add_coin_melt (void *cls,
     247              :                PGresult *result,
     248              :                unsigned int num_results)
     249              : {
     250           10 :   struct CoinHistoryContext *chc = cls;
     251           10 :   struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
     252              : 
     253           20 :   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           10 :     melt = GNUNET_new (struct TALER_EXCHANGEDB_MeltListEntry);
     260              :     {
     261           10 :       struct TALER_DenominationHashP *denom_pub_hashes = NULL;
     262           10 :       struct GNUNET_PQ_ResultSpec rs[] = {
     263           10 :         GNUNET_PQ_result_spec_auto_from_type ("rc",
     264              :                                               &melt->rc),
     265              :         /* oldcoin_index not needed */
     266           10 :         GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
     267              :                                               &melt->h_denom_pub),
     268           10 :         GNUNET_PQ_result_spec_auto_from_type ("old_coin_sig",
     269              :                                               &melt->coin_sig),
     270           10 :         GNUNET_PQ_result_spec_auto_from_type ("refresh_seed",
     271              :                                               &melt->refresh_seed),
     272           10 :         TALER_PQ_result_spec_array_denom_hash (pg->conn,
     273              :                                                "denom_pub_hashes",
     274              :                                                &melt->num_coins,
     275              :                                                &denom_pub_hashes),
     276           10 :         GNUNET_PQ_result_spec_allow_null (
     277           10 :           GNUNET_PQ_result_spec_auto_from_type ("blinding_seed",
     278              :                                                 &melt->blinding_seed),
     279              :           &melt->no_blinding_seed),
     280           10 :         TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
     281              :                                      &melt->amount_with_fee),
     282           10 :         TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh",
     283              :                                      &melt->melt_fee),
     284           10 :         GNUNET_PQ_result_spec_allow_null (
     285           10 :           GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
     286              :                                                 &melt->h_age_commitment),
     287              :           &melt->no_age_commitment),
     288           10 :         GNUNET_PQ_result_spec_uint64 ("refresh_id",
     289              :                                       &serial_id),
     290              :         GNUNET_PQ_result_spec_end
     291              :       };
     292              : 
     293           10 :       if (GNUNET_OK !=
     294           10 :           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           10 :       melt->denom_pub_hashes = denom_pub_hashes;
     305           10 :       denom_pub_hashes = NULL;
     306           10 :       GNUNET_PQ_cleanup_result (rs);
     307              :     }
     308           10 :     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
     309           10 :     tl->next = chc->head;
     310           10 :     tl->type = TALER_EXCHANGEDB_TT_MELT;
     311           10 :     tl->details.melt = melt;
     312           10 :     tl->serial_id = serial_id;
     313           10 :     tl->coin_history_id = chc->chid;
     314           10 :     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           13 : add_coin_refund (void *cls,
     329              :                  PGresult *result,
     330              :                  unsigned int num_results)
     331              : {
     332           13 :   struct CoinHistoryContext *chc = cls;
     333           13 :   struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
     334              : 
     335           26 :   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           13 :     refund = GNUNET_new (struct TALER_EXCHANGEDB_RefundListEntry);
     342              :     {
     343           13 :       struct GNUNET_PQ_ResultSpec rs[] = {
     344           13 :         GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
     345              :                                               &refund->merchant_pub),
     346           13 :         GNUNET_PQ_result_spec_auto_from_type ("merchant_sig",
     347              :                                               &refund->merchant_sig),
     348           13 :         GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
     349              :                                               &refund->h_contract_terms),
     350           13 :         GNUNET_PQ_result_spec_uint64 ("rtransaction_id",
     351              :                                       &refund->rtransaction_id),
     352           13 :         TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
     353              :                                      &refund->refund_amount),
     354           13 :         TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
     355              :                                      &refund->refund_fee),
     356           13 :         GNUNET_PQ_result_spec_uint64 ("refund_serial_id",
     357              :                                       &serial_id),
     358              :         GNUNET_PQ_result_spec_end
     359              :       };
     360              : 
     361           13 :       if (GNUNET_OK !=
     362           13 :           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           13 :     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
     373           13 :     tl->next = chc->head;
     374           13 :     tl->type = TALER_EXCHANGEDB_TT_REFUND;
     375           13 :     tl->details.refund = refund;
     376           13 :     tl->serial_id = serial_id;
     377           13 :     tl->coin_history_id = chc->chid;
     378           13 :     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            1 : add_old_coin_recoup (void *cls,
     451              :                      PGresult *result,
     452              :                      unsigned int num_results)
     453              : {
     454            1 :   struct CoinHistoryContext *chc = cls;
     455            1 :   struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
     456              : 
     457            2 :   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            1 :     recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupRefreshListEntry);
     464              :     {
     465            1 :       struct GNUNET_PQ_ResultSpec rs[] = {
     466            1 :         GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
     467              :                                               &recoup->coin.coin_pub),
     468            1 :         GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
     469              :                                               &recoup->coin_sig),
     470            1 :         GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
     471              :                                               &recoup->coin_blind),
     472            1 :         TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
     473              :                                      &recoup->value),
     474            1 :         GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
     475              :                                          &recoup->timestamp),
     476            1 :         GNUNET_PQ_result_spec_uint32 ("coin_index",
     477              :                                       &recoup->coin_index),
     478            1 :         GNUNET_PQ_result_spec_auto_from_type ("rc",
     479              :                                               &recoup->rc),
     480            1 :         GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
     481              :                                               &recoup->coin.denom_pub_hash),
     482            1 :         TALER_PQ_result_spec_denom_sig ("denom_sig",
     483              :                                         &recoup->coin.denom_sig),
     484            1 :         GNUNET_PQ_result_spec_uint64 ("recoup_refresh_uuid",
     485              :                                       &serial_id),
     486              :         GNUNET_PQ_result_spec_end
     487              :       };
     488              : 
     489            1 :       if (GNUNET_OK !=
     490            1 :           GNUNET_PQ_extract_result (result,
     491              :                                     rs,
     492              :                                     i))
     493              :       {
     494            0 :         GNUNET_break (0);
     495            0 :         GNUNET_free (recoup);
     496            0 :         chc->failed = true;
     497            0 :         return;
     498              :       }
     499            1 :       recoup->old_coin_pub = *chc->coin_pub;
     500              :     }
     501            1 :     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
     502            1 :     tl->next = chc->head;
     503            1 :     tl->type = TALER_EXCHANGEDB_TT_RECOUP_REFRESH_RECEIVER;
     504            1 :     tl->details.old_coin_recoup = recoup;
     505            1 :     tl->serial_id = serial_id;
     506            1 :     tl->coin_history_id = chc->chid;
     507            1 :     chc->head = tl;
     508              :   }
     509              : }
     510              : 
     511              : 
     512              : /**
     513              :  * Function to be called with the results of a SELECT statement
     514              :  * that has returned @a num_results results.
     515              :  *
     516              :  * @param cls closure of type `struct CoinHistoryContext`
     517              :  * @param result the postgres result
     518              :  * @param num_results the number of results in @a result
     519              :  */
     520              : static void
     521           11 : add_coin_recoup (void *cls,
     522              :                  PGresult *result,
     523              :                  unsigned int num_results)
     524              : {
     525           11 :   struct CoinHistoryContext *chc = cls;
     526           11 :   struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
     527              : 
     528           22 :   for (unsigned int i = 0; i<num_results; i++)
     529              :   {
     530              :     struct TALER_EXCHANGEDB_RecoupListEntry *recoup;
     531              :     struct TALER_EXCHANGEDB_TransactionList *tl;
     532              :     uint64_t serial_id;
     533              : 
     534           11 :     recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupListEntry);
     535              :     {
     536           11 :       struct GNUNET_PQ_ResultSpec rs[] = {
     537           11 :         GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
     538              :                                               &recoup->reserve_pub),
     539           11 :         GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
     540              :                                               &recoup->coin_sig),
     541           11 :         GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
     542              :                                               &recoup->h_denom_pub),
     543           11 :         GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
     544              :                                               &recoup->coin_blind),
     545           11 :         TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
     546              :                                      &recoup->value),
     547           11 :         GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
     548              :                                          &recoup->timestamp),
     549           11 :         GNUNET_PQ_result_spec_uint32 ("coin_index",
     550              :                                       &recoup->coin_index),
     551           11 :         GNUNET_PQ_result_spec_auto_from_type ("planchets_h",
     552              :                                               &recoup->planchets_h),
     553           11 :         GNUNET_PQ_result_spec_uint64 ("recoup_uuid",
     554              :                                       &serial_id),
     555              :         GNUNET_PQ_result_spec_end
     556              :       };
     557              : 
     558           11 :       if (GNUNET_OK !=
     559           11 :           GNUNET_PQ_extract_result (result,
     560              :                                     rs,
     561              :                                     i))
     562              :       {
     563            0 :         GNUNET_break (0);
     564            0 :         GNUNET_free (recoup);
     565            0 :         chc->failed = true;
     566            0 :         return;
     567              :       }
     568              :     }
     569           11 :     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
     570           11 :     tl->next = chc->head;
     571           11 :     tl->type = TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW;
     572           11 :     tl->details.recoup = recoup;
     573           11 :     tl->serial_id = serial_id;
     574           11 :     tl->coin_history_id = chc->chid;
     575           11 :     chc->head = tl;
     576              :   }
     577              : }
     578              : 
     579              : 
     580              : /**
     581              :  * Function to be called with the results of a SELECT statement
     582              :  * that has returned @a num_results results.
     583              :  *
     584              :  * @param cls closure of type `struct CoinHistoryContext`
     585              :  * @param result the postgres result
     586              :  * @param num_results the number of results in @a result
     587              :  */
     588              : static void
     589            1 : add_coin_recoup_refresh (void *cls,
     590              :                          PGresult *result,
     591              :                          unsigned int num_results)
     592              : {
     593            1 :   struct CoinHistoryContext *chc = cls;
     594            1 :   struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
     595              : 
     596            2 :   for (unsigned int i = 0; i<num_results; i++)
     597              :   {
     598              :     struct TALER_EXCHANGEDB_RecoupRefreshListEntry *recoup;
     599              :     struct TALER_EXCHANGEDB_TransactionList *tl;
     600              :     uint64_t serial_id;
     601              : 
     602            1 :     recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupRefreshListEntry);
     603              :     {
     604            1 :       struct GNUNET_PQ_ResultSpec rs[] = {
     605            1 :         GNUNET_PQ_result_spec_auto_from_type ("old_coin_pub",
     606              :                                               &recoup->old_coin_pub),
     607            1 :         GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
     608              :                                               &recoup->coin_sig),
     609            1 :         GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
     610              :                                               &recoup->coin_blind),
     611            1 :         TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
     612              :                                      &recoup->value),
     613            1 :         GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
     614              :                                          &recoup->timestamp),
     615            1 :         GNUNET_PQ_result_spec_uint32 ("coin_index",
     616              :                                       &recoup->coin_index),
     617            1 :         GNUNET_PQ_result_spec_auto_from_type ("rc",
     618              :                                               &recoup->rc),
     619            1 :         GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
     620              :                                               &recoup->coin.denom_pub_hash),
     621            1 :         TALER_PQ_result_spec_denom_sig ("denom_sig",
     622              :                                         &recoup->coin.denom_sig),
     623            1 :         GNUNET_PQ_result_spec_uint64 ("recoup_refresh_uuid",
     624              :                                       &serial_id),
     625              :         GNUNET_PQ_result_spec_end
     626              :       };
     627              : 
     628            1 :       if (GNUNET_OK !=
     629            1 :           GNUNET_PQ_extract_result (result,
     630              :                                     rs,
     631              :                                     i))
     632              :       {
     633            0 :         GNUNET_break (0);
     634            0 :         GNUNET_free (recoup);
     635            0 :         chc->failed = true;
     636            0 :         return;
     637              :       }
     638            1 :       recoup->coin.coin_pub = *chc->coin_pub;
     639              :     }
     640            1 :     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
     641            1 :     tl->next = chc->head;
     642            1 :     tl->type = TALER_EXCHANGEDB_TT_RECOUP_REFRESH;
     643            1 :     tl->details.recoup_refresh = recoup;
     644            1 :     tl->serial_id = serial_id;
     645            1 :     tl->coin_history_id = chc->chid;
     646            1 :     chc->head = tl;
     647              :   }
     648              : }
     649              : 
     650              : 
     651              : /**
     652              :  * Function to be called with the results of a SELECT statement
     653              :  * that has returned @a num_results results.
     654              :  *
     655              :  * @param cls closure of type `struct CoinHistoryContext`
     656              :  * @param result the postgres result
     657              :  * @param num_results the number of results in @a result
     658              :  */
     659              : static void
     660            6 : add_coin_reserve_open (void *cls,
     661              :                        PGresult *result,
     662              :                        unsigned int num_results)
     663              : {
     664            6 :   struct CoinHistoryContext *chc = cls;
     665            6 :   struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
     666              : 
     667           12 :   for (unsigned int i = 0; i<num_results; i++)
     668              :   {
     669              :     struct TALER_EXCHANGEDB_ReserveOpenListEntry *role;
     670              :     struct TALER_EXCHANGEDB_TransactionList *tl;
     671              :     uint64_t serial_id;
     672              : 
     673            6 :     role = GNUNET_new (struct TALER_EXCHANGEDB_ReserveOpenListEntry);
     674              :     {
     675            6 :       struct GNUNET_PQ_ResultSpec rs[] = {
     676            6 :         GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
     677              :                                               &role->reserve_sig),
     678            6 :         GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
     679              :                                               &role->coin_sig),
     680            6 :         TALER_PQ_RESULT_SPEC_AMOUNT ("contribution",
     681              :                                      &role->coin_contribution),
     682            6 :         GNUNET_PQ_result_spec_allow_null (
     683            6 :           GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
     684              :                                                 &role->h_age_commitment),
     685              :           &role->no_age_commitment),
     686            6 :         GNUNET_PQ_result_spec_uint64 ("reserve_open_deposit_uuid",
     687              :                                       &serial_id),
     688              :         GNUNET_PQ_result_spec_end
     689              :       };
     690              : 
     691            6 :       if (GNUNET_OK !=
     692            6 :           GNUNET_PQ_extract_result (result,
     693              :                                     rs,
     694              :                                     i))
     695              :       {
     696            0 :         GNUNET_break (0);
     697            0 :         GNUNET_free (role);
     698            0 :         chc->failed = true;
     699            0 :         return;
     700              :       }
     701              :     }
     702            6 :     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
     703            6 :     tl->next = chc->head;
     704            6 :     tl->type = TALER_EXCHANGEDB_TT_RESERVE_OPEN;
     705            6 :     tl->details.reserve_open = role;
     706            6 :     tl->serial_id = serial_id;
     707            6 :     tl->coin_history_id = chc->chid;
     708            6 :     chc->head = tl;
     709              :   }
     710              : }
     711              : 
     712              : 
     713              : /**
     714              :  * Work we need to do.
     715              :  */
     716              : struct Work
     717              : {
     718              :   /**
     719              :    * Name of the table.
     720              :    */
     721              :   const char *table;
     722              : 
     723              :   /**
     724              :    * SQL prepared statement name.
     725              :    */
     726              :   const char *statement;
     727              : 
     728              :   /**
     729              :    * Function to call to handle the result(s).
     730              :    */
     731              :   GNUNET_PQ_PostgresResultHandler cb;
     732              : };
     733              : 
     734              : 
     735              : /**
     736              :  * We found a coin history entry. Lookup details
     737              :  * from the respective table and store in @a cls.
     738              :  *
     739              :  * @param[in,out] cls a `struct CoinHistoryContext`
     740              :  * @param result a coin history entry result set
     741              :  * @param num_results total number of results in @a results
     742              :  */
     743              : static void
     744           82 : handle_history_entry (void *cls,
     745              :                       PGresult *result,
     746              :                       unsigned int num_results)
     747              : {
     748           82 :   struct CoinHistoryContext *chc = cls;
     749           82 :   struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
     750              :   static const struct Work work[] = {
     751              :     [TALER_EXCHANGEDB_TT_DEPOSIT] =
     752              :     { "coin_deposits",
     753              :       "get_coin_transactions_deposit_with_coin_pub",
     754              :       &add_coin_deposit },
     755              :     [TALER_EXCHANGEDB_TT_MELT] =
     756              :     { "refresh",
     757              :       "get_coin_transactions_refresh_by_coin",
     758              :       &add_coin_melt },
     759              :     [TALER_EXCHANGEDB_TT_PURSE_DEPOSIT] =
     760              :     { "purse_deposits",
     761              :       "get_coin_transactions_purse_deposit_by_coin_pub",
     762              :       &add_coin_purse_deposit },
     763              :     [TALER_EXCHANGEDB_TT_PURSE_REFUND] =
     764              :     { "purse_decision",
     765              :       "get_coin_transactions_purse_decision_by_coin_pub",
     766              :       &add_coin_purse_decision },
     767              :     [TALER_EXCHANGEDB_TT_REFUND] =
     768              :     { "refunds",
     769              :       "get_coin_transactions_refunds_by_coin",
     770              :       &add_coin_refund },
     771              :     [TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW] =
     772              :     { "recoup",
     773              :       "get_coin_transactions_recoup_by_coin",
     774              :       &add_coin_recoup },
     775              :     [TALER_EXCHANGEDB_TT_RECOUP_REFRESH] =
     776              :     { "recoup_refresh::NEW",
     777              :       "get_coin_transactions_recoup_by_refreshed_coin",
     778              :       &add_coin_recoup_refresh },
     779              :     [TALER_EXCHANGEDB_TT_RECOUP_REFRESH_RECEIVER] =
     780              :     { "recoup_refresh::OLD",
     781              :       "get_coin_transactions_recoup_by_old_coin",
     782              :       &add_old_coin_recoup },
     783              :     [TALER_EXCHANGEDB_TT_RESERVE_OPEN] =
     784              :     { "reserves_open_deposits",
     785              :       "get_coin_transactions_reserve_open_by_coin",
     786              :       &add_coin_reserve_open },
     787              :     { NULL, NULL, NULL }
     788              :   };
     789              :   char *table_name;
     790              :   uint64_t serial_id;
     791           82 :   struct GNUNET_PQ_ResultSpec rs[] = {
     792           82 :     GNUNET_PQ_result_spec_string ("table_name",
     793              :                                   &table_name),
     794           82 :     GNUNET_PQ_result_spec_uint64 ("serial_id",
     795              :                                   &serial_id),
     796           82 :     GNUNET_PQ_result_spec_uint64 ("coin_history_serial_id",
     797              :                                   &chc->chid),
     798              :     GNUNET_PQ_result_spec_end
     799              :   };
     800           82 :   struct GNUNET_PQ_QueryParam params[] = {
     801           82 :     GNUNET_PQ_query_param_auto_from_type (chc->coin_pub),
     802           82 :     GNUNET_PQ_query_param_uint64 (&serial_id),
     803              :     GNUNET_PQ_query_param_end
     804              :   };
     805              : 
     806          208 :   for (unsigned int i = 0; i<num_results; i++)
     807              :   {
     808              :     enum GNUNET_DB_QueryStatus qs;
     809          126 :     bool found = false;
     810              : 
     811          126 :     if (GNUNET_OK !=
     812          126 :         GNUNET_PQ_extract_result (result,
     813              :                                   rs,
     814              :                                   i))
     815              :     {
     816            0 :       GNUNET_break (0);
     817            0 :       chc->failed = true;
     818            0 :       return;
     819              :     }
     820              : 
     821          126 :     for (unsigned int s = 0;
     822          316 :          NULL != work[s].statement;
     823          190 :          s++)
     824              :     {
     825          506 :       if (0 != strcmp (table_name,
     826          316 :                        work[s].table))
     827          190 :         continue;
     828          126 :       found = true;
     829          126 :       qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
     830          126 :                                                  work[s].statement,
     831              :                                                  params,
     832          126 :                                                  work[s].cb,
     833              :                                                  chc);
     834          126 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     835              :                   "Coin %s had %d transactions at %llu in table %s\n",
     836              :                   TALER_B2S (chc->coin_pub),
     837              :                   (int) qs,
     838              :                   (unsigned long long) serial_id,
     839              :                   table_name);
     840          126 :       if (0 > qs)
     841            0 :         chc->failed = true;
     842          126 :       break;
     843              :     }
     844          126 :     if (! found)
     845              :     {
     846            0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     847              :                   "Coin history includes unsupported table `%s`\n",
     848              :                   table_name);
     849            0 :       chc->failed = true;
     850              :     }
     851          126 :     GNUNET_PQ_cleanup_result (rs);
     852          126 :     if (chc->failed)
     853            0 :       break;
     854              :   }
     855              : }
     856              : 
     857              : 
     858              : enum GNUNET_DB_QueryStatus
     859           87 : TALER_EXCHANGEDB_get_coin_transactions (
     860              :   struct TALER_EXCHANGEDB_PostgresContext *pg,
     861              :   bool begin_transaction,
     862              :   const struct TALER_CoinSpendPublicKeyP *coin_pub,
     863              :   uint64_t start_off,
     864              :   uint64_t etag_in,
     865              :   uint64_t *etag_out,
     866              :   struct TALER_Amount *balance,
     867              :   struct TALER_DenominationHashP *h_denom_pub,
     868              :   struct TALER_EXCHANGEDB_TransactionList **tlp)
     869              : {
     870           87 :   struct GNUNET_PQ_QueryParam params[] = {
     871           87 :     GNUNET_PQ_query_param_auto_from_type (coin_pub),
     872              :     GNUNET_PQ_query_param_end
     873              :   };
     874           87 :   struct GNUNET_PQ_QueryParam lparams[] = {
     875           87 :     GNUNET_PQ_query_param_auto_from_type (coin_pub),
     876           87 :     GNUNET_PQ_query_param_uint64 (&start_off),
     877              :     GNUNET_PQ_query_param_end
     878              :   };
     879           87 :   struct CoinHistoryContext chc = {
     880              :     .head = NULL,
     881              :     .coin_pub = coin_pub,
     882              :     .pg = pg
     883              :   };
     884              : 
     885           87 :   *tlp = NULL;
     886           87 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     887              :               "Getting transactions for coin %s\n",
     888              :               TALER_B2S (coin_pub));
     889           87 :   PREPARE (pg,
     890              :            "get_coin_transactions_history_etag_balance",
     891              :            "SELECT"
     892              :            " ch.coin_history_serial_id"
     893              :            ",kc.remaining"
     894              :            ",denom.denom_pub_hash"
     895              :            " FROM coin_history ch"
     896              :            " JOIN known_coins kc"
     897              :            "   USING (coin_pub)"
     898              :            " JOIN denominations denom"
     899              :            "   USING (denominations_serial)"
     900              :            " WHERE coin_pub=$1"
     901              :            " ORDER BY coin_history_serial_id DESC"
     902              :            " LIMIT 1;");
     903           87 :   PREPARE (pg,
     904              :            "get_coin_transactions_history",
     905              :            "SELECT"
     906              :            " table_name"
     907              :            ",serial_id"
     908              :            ",coin_history_serial_id"
     909              :            " FROM coin_history"
     910              :            " WHERE coin_pub=$1"
     911              :            "   AND coin_history_serial_id > $2"
     912              :            " ORDER BY coin_history_serial_id DESC;");
     913           87 :   PREPARE (pg,
     914              :            "get_coin_transactions_deposit_with_coin_pub",
     915              :            "SELECT"
     916              :            " cdep.amount_with_fee"
     917              :            ",denoms.fee_deposit"
     918              :            ",denoms.denom_pub_hash"
     919              :            ",kc.age_commitment_hash"
     920              :            ",bdep.wallet_timestamp"
     921              :            ",bdep.refund_deadline"
     922              :            ",bdep.wire_deadline"
     923              :            ",bdep.merchant_pub"
     924              :            ",bdep.h_contract_terms"
     925              :            ",bdep.wallet_data_hash"
     926              :            ",bdep.wire_salt"
     927              :            ",wt.payto_uri"
     928              :            ",cdep.coin_sig"
     929              :            ",cdep.coin_deposit_serial_id"
     930              :            ",bdep.done"
     931              :            " FROM coin_deposits cdep"
     932              :            " JOIN batch_deposits bdep"
     933              :            "   USING (batch_deposit_serial_id)"
     934              :            " JOIN wire_targets wt"
     935              :            "   USING (wire_target_h_payto)"
     936              :            " JOIN known_coins kc"
     937              :            "   ON (kc.coin_pub = cdep.coin_pub)"
     938              :            " JOIN denominations denoms"
     939              :            "   USING (denominations_serial)"
     940              :            " WHERE cdep.coin_pub=$1"
     941              :            "   AND cdep.coin_deposit_serial_id=$2;");
     942           87 :   PREPARE (pg,
     943              :            "get_coin_transactions_refresh_by_coin",
     944              :            "SELECT"
     945              :            " rc"
     946              :            ",refresh_seed"
     947              :            ",blinding_seed"
     948              :            ",old_coin_sig"
     949              :            ",amount_with_fee"
     950              :            ",denoms.denom_pub_hash"
     951              :            ",denoms.fee_refresh"
     952              :            ",kc.age_commitment_hash"
     953              :            ",refresh_id"
     954              :            ",ARRAY("
     955              :            " SELECT fresh_denoms.denom_pub_hash"
     956              :            " FROM unnest(refresh.denom_serials) WITH ORDINALITY"
     957              :            " AS fresh(denom_serial, coin_index)"
     958              :            " LEFT JOIN denominations fresh_denoms"
     959              :            " ON fresh_denoms.denominations_serial=fresh.denom_serial"
     960              :            " ORDER BY fresh.coin_index"
     961              :            ") AS denom_pub_hashes"
     962              :            " FROM refresh"
     963              :            " JOIN known_coins kc"
     964              :            "   ON (refresh.old_coin_pub = kc.coin_pub)"
     965              :            " JOIN denominations denoms"
     966              :            "   USING (denominations_serial)"
     967              :            " WHERE old_coin_pub=$1"
     968              :            "   AND refresh_id=$2;");
     969           87 :   PREPARE (pg,
     970              :            "get_coin_transactions_purse_deposit_by_coin_pub",
     971              :            "SELECT"
     972              :            " partner_base_url"
     973              :            ",pd.amount_with_fee"
     974              :            ",denoms.fee_deposit"
     975              :            ",denoms.denom_pub_hash"
     976              :            ",pd.purse_pub"
     977              :            ",kc.age_commitment_hash"
     978              :            ",pd.coin_sig"
     979              :            ",pd.purse_deposit_serial_id"
     980              :            ",pdes.refunded"
     981              :            " FROM purse_deposits pd"
     982              :            " LEFT JOIN partners"
     983              :            "   USING (partner_serial_id)"
     984              :            " JOIN purse_requests pr"
     985              :            "   USING (purse_pub)"
     986              :            " LEFT JOIN purse_decision pdes"
     987              :            "   USING (purse_pub)"
     988              :            " JOIN known_coins kc"
     989              :            "   ON (pd.coin_pub = kc.coin_pub)"
     990              :            " JOIN denominations denoms"
     991              :            "   USING (denominations_serial)"
     992              :            " WHERE pd.purse_deposit_serial_id=$2"
     993              :            "   AND pd.coin_pub=$1;");
     994           87 :   PREPARE (pg,
     995              :            "get_coin_transactions_purse_decision_by_coin_pub",
     996              :            "SELECT"
     997              :            " pdes.purse_pub"
     998              :            ",pd.amount_with_fee"
     999              :            ",denom.fee_refund"
    1000              :            ",pdes.purse_decision_serial_id"
    1001              :            " FROM purse_decision pdes"
    1002              :            " JOIN purse_deposits pd"
    1003              :            "   USING (purse_pub)"
    1004              :            " JOIN known_coins kc"
    1005              :            "   ON (pd.coin_pub = kc.coin_pub)"
    1006              :            " JOIN denominations denom"
    1007              :            "   USING (denominations_serial)"
    1008              :            " WHERE pd.coin_pub=$1"
    1009              :            "   AND pdes.purse_decision_serial_id=$2"
    1010              :            "   AND pdes.refunded;");
    1011           87 :   PREPARE (pg,
    1012              :            "get_coin_transactions_refunds_by_coin",
    1013              :            "SELECT"
    1014              :            " bdep.merchant_pub"
    1015              :            ",ref.merchant_sig"
    1016              :            ",bdep.h_contract_terms"
    1017              :            ",ref.rtransaction_id"
    1018              :            ",ref.amount_with_fee"
    1019              :            ",denom.fee_refund"
    1020              :            ",ref.refund_serial_id"
    1021              :            " FROM refunds ref"
    1022              :            " JOIN coin_deposits cdep"
    1023              :            "   ON (ref.coin_pub = cdep.coin_pub AND ref.batch_deposit_serial_id = cdep.batch_deposit_serial_id)"
    1024              :            " JOIN batch_deposits bdep"
    1025              :            "   ON (ref.batch_deposit_serial_id = bdep.batch_deposit_serial_id)"
    1026              :            " JOIN known_coins kc"
    1027              :            "   ON (ref.coin_pub = kc.coin_pub)"
    1028              :            " JOIN denominations denom"
    1029              :            "   USING (denominations_serial)"
    1030              :            " WHERE ref.coin_pub=$1"
    1031              :            "   AND ref.refund_serial_id=$2;");
    1032           87 :   PREPARE (pg,
    1033              :            "get_coin_transactions_recoup_by_old_coin",
    1034              :            "SELECT"
    1035              :            " coins.coin_pub"
    1036              :            ",rr.coin_sig"
    1037              :            ",rr.coin_blind"
    1038              :            ",rr.amount"
    1039              :            ",rr.recoup_timestamp"
    1040              :            ",rr.coin_index"
    1041              :            ",rfc.rc"
    1042              :            ",denoms.denom_pub_hash"
    1043              :            ",coins.denom_sig"
    1044              :            ",rr.recoup_refresh_uuid"
    1045              :            " FROM recoup_refresh rr"
    1046              :            " JOIN refresh rfc"
    1047              :            "   ON (rfc.refresh_id = rr.refresh_id)"
    1048              :            " JOIN known_coins coins"
    1049              :            "   ON (coins.coin_pub = rr.coin_pub)"
    1050              :            " JOIN denominations denoms"
    1051              :            "   ON (denoms.denominations_serial = coins.denominations_serial)"
    1052              :            " WHERE rr.recoup_refresh_uuid=$2"
    1053              :            "   AND rfc.old_coin_pub=$1;");
    1054           87 :   PREPARE (pg,
    1055              :            "get_coin_transactions_recoup_by_coin",
    1056              :            "SELECT"
    1057              :            " res.reserve_pub"
    1058              :            ",denoms.denom_pub_hash"
    1059              :            ",rcp.coin_sig"
    1060              :            ",rcp.coin_blind"
    1061              :            ",rcp.amount"
    1062              :            ",rcp.recoup_timestamp"
    1063              :            ",rcp.coin_index"
    1064              :            ",ro.planchets_h"
    1065              :            ",rcp.recoup_uuid"
    1066              :            " FROM recoup rcp"
    1067              :            " JOIN withdraw ro"
    1068              :            "   USING (withdraw_id)"
    1069              :            " JOIN reserves res"
    1070              :            "   USING (reserve_pub)"
    1071              :            " JOIN known_coins coins"
    1072              :            "   USING (coin_pub)"
    1073              :            " JOIN denominations denoms"
    1074              :            "   ON (denoms.denominations_serial = coins.denominations_serial)"
    1075              :            " WHERE rcp.recoup_uuid=$2"
    1076              :            "   AND coins.coin_pub=$1;");
    1077              :   /* Used to obtain recoup transactions
    1078              :      for a refreshed coin */
    1079           87 :   PREPARE (pg,
    1080              :            "get_coin_transactions_recoup_by_refreshed_coin",
    1081              :            "SELECT"
    1082              :            " old_coins.coin_pub AS old_coin_pub"
    1083              :            ",rr.coin_sig"
    1084              :            ",rr.coin_blind"
    1085              :            ",rr.amount"
    1086              :            ",rr.recoup_timestamp"
    1087              :            ",rr.coin_index"
    1088              :            ",rfc.rc"
    1089              :            ",denoms.denom_pub_hash"
    1090              :            ",coins.denom_sig"
    1091              :            ",recoup_refresh_uuid"
    1092              :            " FROM recoup_refresh rr"
    1093              :            "    JOIN refresh rfc"
    1094              :            "      ON (rr.refresh_id = rfc.refresh_id)"
    1095              :            "    JOIN known_coins old_coins"
    1096              :            "      ON (rfc.old_coin_pub = old_coins.coin_pub)"
    1097              :            "    JOIN known_coins coins"
    1098              :            "      ON (rr.coin_pub = coins.coin_pub)"
    1099              :            "    JOIN denominations denoms"
    1100              :            "      ON (denoms.denominations_serial = coins.denominations_serial)"
    1101              :            " WHERE rr.recoup_refresh_uuid=$2"
    1102              :            "   AND coins.coin_pub=$1;");
    1103           87 :   PREPARE (pg,
    1104              :            "get_coin_transactions_reserve_open_by_coin",
    1105              :            "SELECT"
    1106              :            " rod.reserve_open_deposit_uuid"
    1107              :            ",rod.coin_sig"
    1108              :            ",rod.reserve_sig"
    1109              :            ",rod.contribution"
    1110              :            ",kc.age_commitment_hash"
    1111              :            " FROM reserves_open_deposits rod"
    1112              :            " JOIN known_coins kc"
    1113              :            "   ON (rod.coin_pub = kc.coin_pub)"
    1114              :            " WHERE rod.coin_pub=$1"
    1115              :            "   AND rod.reserve_open_deposit_uuid=$2;");
    1116           87 :   for (unsigned int i = 0; i<RETRIES; i++)
    1117              :   {
    1118              :     enum GNUNET_DB_QueryStatus qs;
    1119              :     uint64_t end;
    1120           87 :     struct GNUNET_PQ_ResultSpec rs[] = {
    1121           87 :       GNUNET_PQ_result_spec_uint64 ("coin_history_serial_id",
    1122              :                                     &end),
    1123           87 :       GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
    1124              :                                             h_denom_pub),
    1125           87 :       TALER_PQ_RESULT_SPEC_AMOUNT ("remaining",
    1126              :                                    balance),
    1127              :       GNUNET_PQ_result_spec_end
    1128              :     };
    1129              : 
    1130           87 :     if (begin_transaction)
    1131              :     {
    1132           24 :       if (GNUNET_OK !=
    1133           24 :           TALER_EXCHANGEDB_start_read_committed (pg,
    1134              :                                                  "get-coin-transactions"))
    1135              :       {
    1136            0 :         GNUNET_break (0);
    1137           87 :         return GNUNET_DB_STATUS_HARD_ERROR;
    1138              :       }
    1139              :     }
    1140              :     /* First only check the last item, to see if
    1141              :        we even need to iterate */
    1142           87 :     qs = GNUNET_PQ_eval_prepared_singleton_select (
    1143              :       pg->conn,
    1144              :       "get_coin_transactions_history_etag_balance",
    1145              :       params,
    1146              :       rs);
    1147           87 :     switch (qs)
    1148              :     {
    1149            0 :     case GNUNET_DB_STATUS_HARD_ERROR:
    1150            0 :       if (begin_transaction)
    1151            0 :         TALER_EXCHANGEDB_rollback (pg);
    1152            0 :       return qs;
    1153            0 :     case GNUNET_DB_STATUS_SOFT_ERROR:
    1154            0 :       if (begin_transaction)
    1155            0 :         TALER_EXCHANGEDB_rollback (pg);
    1156            0 :       continue;
    1157            4 :     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    1158            4 :       if (begin_transaction)
    1159            4 :         TALER_EXCHANGEDB_rollback (pg);
    1160            4 :       return qs;
    1161           83 :     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    1162           83 :       *etag_out = end;
    1163           83 :       if (end == etag_in)
    1164              :       {
    1165              :         /* client is up-to-date, nothing more to do */
    1166            1 :         if (begin_transaction)
    1167            1 :           TALER_EXCHANGEDB_rollback (pg);
    1168            1 :         return qs;
    1169              :       }
    1170              :     }
    1171              :     /* We indeed need to iterate over the history */
    1172           82 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1173              :                 "Current ETag for coin %s is %llu\n",
    1174              :                 TALER_B2S (coin_pub),
    1175              :                 (unsigned long long) end);
    1176              : 
    1177           82 :     qs = GNUNET_PQ_eval_prepared_multi_select (
    1178              :       pg->conn,
    1179              :       "get_coin_transactions_history",
    1180              :       lparams,
    1181              :       &handle_history_entry,
    1182              :       &chc);
    1183           82 :     switch (qs)
    1184              :     {
    1185            0 :     case GNUNET_DB_STATUS_HARD_ERROR:
    1186            0 :       if (begin_transaction)
    1187            0 :         TALER_EXCHANGEDB_rollback (pg);
    1188            0 :       return qs;
    1189            0 :     case GNUNET_DB_STATUS_SOFT_ERROR:
    1190            0 :       if (begin_transaction)
    1191            0 :         TALER_EXCHANGEDB_rollback (pg);
    1192            0 :       TALER_EXCHANGEDB_free_coin_transaction_list (chc.head);
    1193            0 :       chc.head = NULL;
    1194            0 :       continue;
    1195           82 :     default:
    1196           82 :       break;
    1197              :     }
    1198           82 :     if (chc.failed)
    1199              :     {
    1200            0 :       if (begin_transaction)
    1201            0 :         TALER_EXCHANGEDB_rollback (pg);
    1202            0 :       TALER_EXCHANGEDB_free_coin_transaction_list (chc.head);
    1203            0 :       return GNUNET_DB_STATUS_SOFT_ERROR;
    1204              :     }
    1205           82 :     if (! begin_transaction)
    1206              :     {
    1207           63 :       *tlp = chc.head;
    1208           63 :       return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
    1209              :     }
    1210           19 :     qs = TALER_EXCHANGEDB_commit (pg);
    1211           19 :     switch (qs)
    1212              :     {
    1213            0 :     case GNUNET_DB_STATUS_HARD_ERROR:
    1214            0 :       TALER_EXCHANGEDB_free_coin_transaction_list (chc.head);
    1215            0 :       chc.head = NULL;
    1216            0 :       return qs;
    1217            0 :     case GNUNET_DB_STATUS_SOFT_ERROR:
    1218            0 :       TALER_EXCHANGEDB_free_coin_transaction_list (chc.head);
    1219            0 :       chc.head = NULL;
    1220            0 :       continue;
    1221           19 :     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    1222              :     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    1223           19 :       *tlp = chc.head;
    1224           19 :       return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
    1225              :     }
    1226              :   }
    1227            0 :   return GNUNET_DB_STATUS_SOFT_ERROR;
    1228              : }
        

Generated by: LCOV version 2.0-1