exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

get_coin_transactions.c (41454B)


      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 add_coin_deposit (void *cls,
     81                   PGresult *result,
     82                   unsigned int num_results)
     83 {
     84   struct CoinHistoryContext *chc = cls;
     85   struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
     86 
     87   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     deposit = GNUNET_new (struct TALER_EXCHANGEDB_DepositListEntry);
     94     {
     95       struct GNUNET_PQ_ResultSpec rs[] = {
     96         TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
     97                                      &deposit->amount_with_fee),
     98         TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
     99                                      &deposit->deposit_fee),
    100         GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
    101                                               &deposit->h_denom_pub),
    102         GNUNET_PQ_result_spec_allow_null (
    103           GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
    104                                                 &deposit->h_age_commitment),
    105           &deposit->no_age_commitment),
    106         GNUNET_PQ_result_spec_allow_null (
    107           GNUNET_PQ_result_spec_auto_from_type ("wallet_data_hash",
    108                                                 &deposit->wallet_data_hash),
    109           &deposit->no_wallet_data_hash),
    110         GNUNET_PQ_result_spec_timestamp ("wallet_timestamp",
    111                                          &deposit->timestamp),
    112         GNUNET_PQ_result_spec_timestamp ("refund_deadline",
    113                                          &deposit->refund_deadline),
    114         GNUNET_PQ_result_spec_timestamp ("wire_deadline",
    115                                          &deposit->wire_deadline),
    116         GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
    117                                               &deposit->merchant_pub),
    118         GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
    119                                               &deposit->h_contract_terms),
    120         GNUNET_PQ_result_spec_auto_from_type ("wire_salt",
    121                                               &deposit->wire_salt),
    122         GNUNET_PQ_result_spec_string ("payto_uri",
    123                                       &deposit->receiver_wire_account.full_payto
    124                                       ),
    125         GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
    126                                               &deposit->csig),
    127         GNUNET_PQ_result_spec_uint64 ("coin_deposit_serial_id",
    128                                       &serial_id),
    129         GNUNET_PQ_result_spec_auto_from_type ("done",
    130                                               &deposit->done),
    131         GNUNET_PQ_result_spec_end
    132       };
    133 
    134       if (GNUNET_OK !=
    135           GNUNET_PQ_extract_result (result,
    136                                     rs,
    137                                     i))
    138       {
    139         GNUNET_break (0);
    140         GNUNET_free (deposit);
    141         chc->failed = true;
    142         return;
    143       }
    144     }
    145     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
    146     tl->next = chc->head;
    147     tl->type = TALER_EXCHANGEDB_TT_DEPOSIT;
    148     tl->details.deposit = deposit;
    149     tl->serial_id = serial_id;
    150     tl->coin_history_id = chc->chid;
    151     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 add_coin_purse_deposit (void *cls,
    166                         PGresult *result,
    167                         unsigned int num_results)
    168 {
    169   struct CoinHistoryContext *chc = cls;
    170   struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
    171 
    172   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     deposit = GNUNET_new (struct TALER_EXCHANGEDB_PurseDepositListEntry);
    179     {
    180       bool not_finished;
    181       struct GNUNET_PQ_ResultSpec rs[] = {
    182         TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
    183                                      &deposit->amount),
    184         TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
    185                                      &deposit->deposit_fee),
    186         GNUNET_PQ_result_spec_auto_from_type ("purse_pub",
    187                                               &deposit->purse_pub),
    188         GNUNET_PQ_result_spec_uint64 ("purse_deposit_serial_id",
    189                                       &serial_id),
    190         GNUNET_PQ_result_spec_allow_null (
    191           GNUNET_PQ_result_spec_string ("partner_base_url",
    192                                         &deposit->exchange_base_url),
    193           NULL),
    194         GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
    195                                               &deposit->coin_sig),
    196         GNUNET_PQ_result_spec_allow_null (
    197           GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
    198                                                 &deposit->h_age_commitment),
    199           &deposit->no_age_commitment),
    200         GNUNET_PQ_result_spec_allow_null (
    201           GNUNET_PQ_result_spec_bool ("refunded",
    202                                       &deposit->refunded),
    203           &not_finished),
    204         GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
    205                                               &deposit->h_denom_pub),
    206         GNUNET_PQ_result_spec_end
    207       };
    208 
    209       if (GNUNET_OK !=
    210           GNUNET_PQ_extract_result (result,
    211                                     rs,
    212                                     i))
    213       {
    214         GNUNET_break (0);
    215         GNUNET_free (deposit);
    216         chc->failed = true;
    217         return;
    218       }
    219       if (not_finished)
    220         deposit->refunded = false;
    221       /* double-check for all-zeros age commitment */
    222       if (! deposit->no_age_commitment)
    223         deposit->no_age_commitment
    224           = GNUNET_is_zero (&deposit->h_age_commitment);
    225     }
    226     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
    227     tl->next = chc->head;
    228     tl->type = TALER_EXCHANGEDB_TT_PURSE_DEPOSIT;
    229     tl->details.purse_deposit = deposit;
    230     tl->serial_id = serial_id;
    231     tl->coin_history_id = chc->chid;
    232     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 add_coin_melt (void *cls,
    247                PGresult *result,
    248                unsigned int num_results)
    249 {
    250   struct CoinHistoryContext *chc = cls;
    251   struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
    252 
    253   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     melt = GNUNET_new (struct TALER_EXCHANGEDB_MeltListEntry);
    260     {
    261       struct TALER_DenominationHashP *denom_pub_hashes = NULL;
    262       struct GNUNET_PQ_ResultSpec rs[] = {
    263         GNUNET_PQ_result_spec_auto_from_type ("rc",
    264                                               &melt->rc),
    265         /* oldcoin_index not needed */
    266         GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
    267                                               &melt->h_denom_pub),
    268         GNUNET_PQ_result_spec_auto_from_type ("old_coin_sig",
    269                                               &melt->coin_sig),
    270         GNUNET_PQ_result_spec_auto_from_type ("refresh_seed",
    271                                               &melt->refresh_seed),
    272         TALER_PQ_result_spec_array_denom_hash (pg->conn,
    273                                                "denom_pub_hashes",
    274                                                &melt->num_coins,
    275                                                &denom_pub_hashes),
    276         GNUNET_PQ_result_spec_allow_null (
    277           GNUNET_PQ_result_spec_auto_from_type ("blinding_seed",
    278                                                 &melt->blinding_seed),
    279           &melt->no_blinding_seed),
    280         TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
    281                                      &melt->amount_with_fee),
    282         TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh",
    283                                      &melt->melt_fee),
    284         GNUNET_PQ_result_spec_allow_null (
    285           GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
    286                                                 &melt->h_age_commitment),
    287           &melt->no_age_commitment),
    288         GNUNET_PQ_result_spec_uint64 ("refresh_id",
    289                                       &serial_id),
    290         GNUNET_PQ_result_spec_end
    291       };
    292 
    293       if (GNUNET_OK !=
    294           GNUNET_PQ_extract_result (result,
    295                                     rs,
    296                                     i))
    297       {
    298         GNUNET_break (0);
    299         GNUNET_PQ_cleanup_result (rs);
    300         GNUNET_free (melt);
    301         chc->failed = true;
    302         return;
    303       }
    304       melt->denom_pub_hashes = denom_pub_hashes;
    305       denom_pub_hashes = NULL;
    306       GNUNET_PQ_cleanup_result (rs);
    307     }
    308     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
    309     tl->next = chc->head;
    310     tl->type = TALER_EXCHANGEDB_TT_MELT;
    311     tl->details.melt = melt;
    312     tl->serial_id = serial_id;
    313     tl->coin_history_id = chc->chid;
    314     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 add_coin_refund (void *cls,
    329                  PGresult *result,
    330                  unsigned int num_results)
    331 {
    332   struct CoinHistoryContext *chc = cls;
    333   struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
    334 
    335   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     refund = GNUNET_new (struct TALER_EXCHANGEDB_RefundListEntry);
    342     {
    343       struct GNUNET_PQ_ResultSpec rs[] = {
    344         GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
    345                                               &refund->merchant_pub),
    346         GNUNET_PQ_result_spec_auto_from_type ("merchant_sig",
    347                                               &refund->merchant_sig),
    348         GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
    349                                               &refund->h_contract_terms),
    350         GNUNET_PQ_result_spec_uint64 ("rtransaction_id",
    351                                       &refund->rtransaction_id),
    352         TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
    353                                      &refund->refund_amount),
    354         TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
    355                                      &refund->refund_fee),
    356         GNUNET_PQ_result_spec_uint64 ("refund_serial_id",
    357                                       &serial_id),
    358         GNUNET_PQ_result_spec_end
    359       };
    360 
    361       if (GNUNET_OK !=
    362           GNUNET_PQ_extract_result (result,
    363                                     rs,
    364                                     i))
    365       {
    366         GNUNET_break (0);
    367         GNUNET_free (refund);
    368         chc->failed = true;
    369         return;
    370       }
    371     }
    372     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
    373     tl->next = chc->head;
    374     tl->type = TALER_EXCHANGEDB_TT_REFUND;
    375     tl->details.refund = refund;
    376     tl->serial_id = serial_id;
    377     tl->coin_history_id = chc->chid;
    378     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 add_coin_purse_decision (void *cls,
    393                          PGresult *result,
    394                          unsigned int num_results)
    395 {
    396   struct CoinHistoryContext *chc = cls;
    397   struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
    398 
    399   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     prefund = GNUNET_new (struct TALER_EXCHANGEDB_PurseRefundListEntry);
    406     {
    407       struct GNUNET_PQ_ResultSpec rs[] = {
    408         GNUNET_PQ_result_spec_auto_from_type ("purse_pub",
    409                                               &prefund->purse_pub),
    410         TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
    411                                      &prefund->refund_amount),
    412         TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
    413                                      &prefund->refund_fee),
    414         GNUNET_PQ_result_spec_uint64 ("purse_decision_serial_id",
    415                                       &serial_id),
    416         GNUNET_PQ_result_spec_end
    417       };
    418 
    419       if (GNUNET_OK !=
    420           GNUNET_PQ_extract_result (result,
    421                                     rs,
    422                                     i))
    423       {
    424         GNUNET_break (0);
    425         GNUNET_free (prefund);
    426         chc->failed = true;
    427         return;
    428       }
    429     }
    430     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
    431     tl->next = chc->head;
    432     tl->type = TALER_EXCHANGEDB_TT_PURSE_REFUND;
    433     tl->details.purse_refund = prefund;
    434     tl->serial_id = serial_id;
    435     tl->coin_history_id = chc->chid;
    436     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 add_old_coin_recoup (void *cls,
    451                      PGresult *result,
    452                      unsigned int num_results)
    453 {
    454   struct CoinHistoryContext *chc = cls;
    455   struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
    456 
    457   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     recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupRefreshListEntry);
    464     {
    465       struct GNUNET_PQ_ResultSpec rs[] = {
    466         GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
    467                                               &recoup->coin.coin_pub),
    468         GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
    469                                               &recoup->coin_sig),
    470         GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
    471                                               &recoup->coin_blind),
    472         TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
    473                                      &recoup->value),
    474         GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
    475                                          &recoup->timestamp),
    476         GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
    477                                               &recoup->coin.denom_pub_hash),
    478         TALER_PQ_result_spec_denom_sig ("denom_sig",
    479                                         &recoup->coin.denom_sig),
    480         GNUNET_PQ_result_spec_uint64 ("recoup_refresh_uuid",
    481                                       &serial_id),
    482         GNUNET_PQ_result_spec_end
    483       };
    484 
    485       if (GNUNET_OK !=
    486           GNUNET_PQ_extract_result (result,
    487                                     rs,
    488                                     i))
    489       {
    490         GNUNET_break (0);
    491         GNUNET_free (recoup);
    492         chc->failed = true;
    493         return;
    494       }
    495       recoup->old_coin_pub = *chc->coin_pub;
    496     }
    497     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
    498     tl->next = chc->head;
    499     tl->type = TALER_EXCHANGEDB_TT_RECOUP_REFRESH_RECEIVER;
    500     tl->details.old_coin_recoup = recoup;
    501     tl->serial_id = serial_id;
    502     tl->coin_history_id = chc->chid;
    503     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 add_coin_recoup (void *cls,
    518                  PGresult *result,
    519                  unsigned int num_results)
    520 {
    521   struct CoinHistoryContext *chc = cls;
    522   struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
    523 
    524   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     recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupListEntry);
    531     {
    532       struct GNUNET_PQ_ResultSpec rs[] = {
    533         GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
    534                                               &recoup->reserve_pub),
    535         GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
    536                                               &recoup->coin_sig),
    537         GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
    538                                               &recoup->h_denom_pub),
    539         GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
    540                                               &recoup->coin_blind),
    541         TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
    542                                      &recoup->value),
    543         GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
    544                                          &recoup->timestamp),
    545         GNUNET_PQ_result_spec_uint64 ("recoup_uuid",
    546                                       &serial_id),
    547         GNUNET_PQ_result_spec_end
    548       };
    549 
    550       if (GNUNET_OK !=
    551           GNUNET_PQ_extract_result (result,
    552                                     rs,
    553                                     i))
    554       {
    555         GNUNET_break (0);
    556         GNUNET_free (recoup);
    557         chc->failed = true;
    558         return;
    559       }
    560     }
    561     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
    562     tl->next = chc->head;
    563     tl->type = TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW;
    564     tl->details.recoup = recoup;
    565     tl->serial_id = serial_id;
    566     tl->coin_history_id = chc->chid;
    567     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 add_coin_recoup_refresh (void *cls,
    582                          PGresult *result,
    583                          unsigned int num_results)
    584 {
    585   struct CoinHistoryContext *chc = cls;
    586   struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
    587 
    588   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     recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupRefreshListEntry);
    595     {
    596       struct GNUNET_PQ_ResultSpec rs[] = {
    597         GNUNET_PQ_result_spec_auto_from_type ("old_coin_pub",
    598                                               &recoup->old_coin_pub),
    599         GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
    600                                               &recoup->coin_sig),
    601         GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
    602                                               &recoup->coin_blind),
    603         TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
    604                                      &recoup->value),
    605         GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
    606                                          &recoup->timestamp),
    607         GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
    608                                               &recoup->coin.denom_pub_hash),
    609         TALER_PQ_result_spec_denom_sig ("denom_sig",
    610                                         &recoup->coin.denom_sig),
    611         GNUNET_PQ_result_spec_uint64 ("recoup_refresh_uuid",
    612                                       &serial_id),
    613         GNUNET_PQ_result_spec_end
    614       };
    615 
    616       if (GNUNET_OK !=
    617           GNUNET_PQ_extract_result (result,
    618                                     rs,
    619                                     i))
    620       {
    621         GNUNET_break (0);
    622         GNUNET_free (recoup);
    623         chc->failed = true;
    624         return;
    625       }
    626       recoup->coin.coin_pub = *chc->coin_pub;
    627     }
    628     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
    629     tl->next = chc->head;
    630     tl->type = TALER_EXCHANGEDB_TT_RECOUP_REFRESH;
    631     tl->details.recoup_refresh = recoup;
    632     tl->serial_id = serial_id;
    633     tl->coin_history_id = chc->chid;
    634     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 add_coin_reserve_open (void *cls,
    649                        PGresult *result,
    650                        unsigned int num_results)
    651 {
    652   struct CoinHistoryContext *chc = cls;
    653   struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
    654 
    655   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     role = GNUNET_new (struct TALER_EXCHANGEDB_ReserveOpenListEntry);
    662     {
    663       struct GNUNET_PQ_ResultSpec rs[] = {
    664         GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
    665                                               &role->reserve_sig),
    666         GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
    667                                               &role->coin_sig),
    668         TALER_PQ_RESULT_SPEC_AMOUNT ("contribution",
    669                                      &role->coin_contribution),
    670         GNUNET_PQ_result_spec_allow_null (
    671           GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
    672                                                 &role->h_age_commitment),
    673           &role->no_age_commitment),
    674         GNUNET_PQ_result_spec_uint64 ("reserve_open_deposit_uuid",
    675                                       &serial_id),
    676         GNUNET_PQ_result_spec_end
    677       };
    678 
    679       if (GNUNET_OK !=
    680           GNUNET_PQ_extract_result (result,
    681                                     rs,
    682                                     i))
    683       {
    684         GNUNET_break (0);
    685         GNUNET_free (role);
    686         chc->failed = true;
    687         return;
    688       }
    689     }
    690     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
    691     tl->next = chc->head;
    692     tl->type = TALER_EXCHANGEDB_TT_RESERVE_OPEN;
    693     tl->details.reserve_open = role;
    694     tl->serial_id = serial_id;
    695     tl->coin_history_id = chc->chid;
    696     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 handle_history_entry (void *cls,
    733                       PGresult *result,
    734                       unsigned int num_results)
    735 {
    736   struct CoinHistoryContext *chc = cls;
    737   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   struct GNUNET_PQ_ResultSpec rs[] = {
    780     GNUNET_PQ_result_spec_string ("table_name",
    781                                   &table_name),
    782     GNUNET_PQ_result_spec_uint64 ("serial_id",
    783                                   &serial_id),
    784     GNUNET_PQ_result_spec_uint64 ("coin_history_serial_id",
    785                                   &chc->chid),
    786     GNUNET_PQ_result_spec_end
    787   };
    788   struct GNUNET_PQ_QueryParam params[] = {
    789     GNUNET_PQ_query_param_auto_from_type (chc->coin_pub),
    790     GNUNET_PQ_query_param_uint64 (&serial_id),
    791     GNUNET_PQ_query_param_end
    792   };
    793 
    794   for (unsigned int i = 0; i<num_results; i++)
    795   {
    796     enum GNUNET_DB_QueryStatus qs;
    797     bool found = false;
    798 
    799     if (GNUNET_OK !=
    800         GNUNET_PQ_extract_result (result,
    801                                   rs,
    802                                   i))
    803     {
    804       GNUNET_break (0);
    805       chc->failed = true;
    806       return;
    807     }
    808 
    809     for (unsigned int s = 0;
    810          NULL != work[s].statement;
    811          s++)
    812     {
    813       if (0 != strcmp (table_name,
    814                        work[s].table))
    815         continue;
    816       found = true;
    817       qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
    818                                                  work[s].statement,
    819                                                  params,
    820                                                  work[s].cb,
    821                                                  chc);
    822       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       if (0 > qs)
    829         chc->failed = true;
    830       break;
    831     }
    832     if (! found)
    833     {
    834       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    835                   "Coin history includes unsupported table `%s`\n",
    836                   table_name);
    837       chc->failed = true;
    838     }
    839     GNUNET_PQ_cleanup_result (rs);
    840     if (chc->failed)
    841       break;
    842   }
    843 }
    844 
    845 
    846 enum GNUNET_DB_QueryStatus
    847 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   struct GNUNET_PQ_QueryParam params[] = {
    859     GNUNET_PQ_query_param_auto_from_type (coin_pub),
    860     GNUNET_PQ_query_param_end
    861   };
    862   struct GNUNET_PQ_QueryParam lparams[] = {
    863     GNUNET_PQ_query_param_auto_from_type (coin_pub),
    864     GNUNET_PQ_query_param_uint64 (&start_off),
    865     GNUNET_PQ_query_param_end
    866   };
    867   struct CoinHistoryContext chc = {
    868     .head = NULL,
    869     .coin_pub = coin_pub,
    870     .pg = pg
    871   };
    872 
    873   *tlp = NULL;
    874   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    875               "Getting transactions for coin %s\n",
    876               TALER_B2S (coin_pub));
    877   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   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   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   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   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   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   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   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   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   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   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   for (unsigned int i = 0; i<RETRIES; i++)
   1100   {
   1101     enum GNUNET_DB_QueryStatus qs;
   1102     uint64_t end;
   1103     struct GNUNET_PQ_ResultSpec rs[] = {
   1104       GNUNET_PQ_result_spec_uint64 ("coin_history_serial_id",
   1105                                     &end),
   1106       GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
   1107                                             h_denom_pub),
   1108       TALER_PQ_RESULT_SPEC_AMOUNT ("remaining",
   1109                                    balance),
   1110       GNUNET_PQ_result_spec_end
   1111     };
   1112 
   1113     if (begin_transaction)
   1114     {
   1115       if (GNUNET_OK !=
   1116           TALER_EXCHANGEDB_start_read_committed (pg,
   1117                                                  "get-coin-transactions"))
   1118       {
   1119         GNUNET_break (0);
   1120         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     qs = GNUNET_PQ_eval_prepared_singleton_select (
   1126       pg->conn,
   1127       "get_coin_transactions_history_etag_balance",
   1128       params,
   1129       rs);
   1130     switch (qs)
   1131     {
   1132     case GNUNET_DB_STATUS_HARD_ERROR:
   1133       if (begin_transaction)
   1134         TALER_EXCHANGEDB_rollback (pg);
   1135       return qs;
   1136     case GNUNET_DB_STATUS_SOFT_ERROR:
   1137       if (begin_transaction)
   1138         TALER_EXCHANGEDB_rollback (pg);
   1139       continue;
   1140     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
   1141       if (begin_transaction)
   1142         TALER_EXCHANGEDB_rollback (pg);
   1143       return qs;
   1144     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
   1145       *etag_out = end;
   1146       if (end == etag_in)
   1147       {
   1148         /* client is up-to-date, nothing more to do */
   1149         if (begin_transaction)
   1150           TALER_EXCHANGEDB_rollback (pg);
   1151         return qs;
   1152       }
   1153     }
   1154     /* We indeed need to iterate over the history */
   1155     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     qs = GNUNET_PQ_eval_prepared_multi_select (
   1161       pg->conn,
   1162       "get_coin_transactions_history",
   1163       lparams,
   1164       &handle_history_entry,
   1165       &chc);
   1166     switch (qs)
   1167     {
   1168     case GNUNET_DB_STATUS_HARD_ERROR:
   1169       if (begin_transaction)
   1170         TALER_EXCHANGEDB_rollback (pg);
   1171       return qs;
   1172     case GNUNET_DB_STATUS_SOFT_ERROR:
   1173       if (begin_transaction)
   1174         TALER_EXCHANGEDB_rollback (pg);
   1175       TALER_EXCHANGEDB_free_coin_transaction_list (chc.head);
   1176       chc.head = NULL;
   1177       continue;
   1178     default:
   1179       break;
   1180     }
   1181     if (chc.failed)
   1182     {
   1183       if (begin_transaction)
   1184         TALER_EXCHANGEDB_rollback (pg);
   1185       TALER_EXCHANGEDB_free_coin_transaction_list (chc.head);
   1186       return GNUNET_DB_STATUS_SOFT_ERROR;
   1187     }
   1188     if (! begin_transaction)
   1189     {
   1190       *tlp = chc.head;
   1191       return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
   1192     }
   1193     qs = TALER_EXCHANGEDB_commit (pg);
   1194     switch (qs)
   1195     {
   1196     case GNUNET_DB_STATUS_HARD_ERROR:
   1197       TALER_EXCHANGEDB_free_coin_transaction_list (chc.head);
   1198       chc.head = NULL;
   1199       return qs;
   1200     case GNUNET_DB_STATUS_SOFT_ERROR:
   1201       TALER_EXCHANGEDB_free_coin_transaction_list (chc.head);
   1202       chc.head = NULL;
   1203       continue;
   1204     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
   1205     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
   1206       *tlp = chc.head;
   1207       return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
   1208     }
   1209   }
   1210   return GNUNET_DB_STATUS_SOFT_ERROR;
   1211 }