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 (42329B)


      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_uint32 ("coin_index",
    477                                       &recoup->coin_index),
    478         GNUNET_PQ_result_spec_auto_from_type ("rc",
    479                                               &recoup->rc),
    480         GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
    481                                               &recoup->coin.denom_pub_hash),
    482         TALER_PQ_result_spec_denom_sig ("denom_sig",
    483                                         &recoup->coin.denom_sig),
    484         GNUNET_PQ_result_spec_uint64 ("recoup_refresh_uuid",
    485                                       &serial_id),
    486         GNUNET_PQ_result_spec_end
    487       };
    488 
    489       if (GNUNET_OK !=
    490           GNUNET_PQ_extract_result (result,
    491                                     rs,
    492                                     i))
    493       {
    494         GNUNET_break (0);
    495         GNUNET_free (recoup);
    496         chc->failed = true;
    497         return;
    498       }
    499       recoup->old_coin_pub = *chc->coin_pub;
    500     }
    501     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
    502     tl->next = chc->head;
    503     tl->type = TALER_EXCHANGEDB_TT_RECOUP_REFRESH_RECEIVER;
    504     tl->details.old_coin_recoup = recoup;
    505     tl->serial_id = serial_id;
    506     tl->coin_history_id = chc->chid;
    507     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 add_coin_recoup (void *cls,
    522                  PGresult *result,
    523                  unsigned int num_results)
    524 {
    525   struct CoinHistoryContext *chc = cls;
    526   struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
    527 
    528   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     recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupListEntry);
    535     {
    536       struct GNUNET_PQ_ResultSpec rs[] = {
    537         GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
    538                                               &recoup->reserve_pub),
    539         GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
    540                                               &recoup->coin_sig),
    541         GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
    542                                               &recoup->h_denom_pub),
    543         GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
    544                                               &recoup->coin_blind),
    545         TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
    546                                      &recoup->value),
    547         GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
    548                                          &recoup->timestamp),
    549         GNUNET_PQ_result_spec_uint32 ("coin_index",
    550                                       &recoup->coin_index),
    551         GNUNET_PQ_result_spec_auto_from_type ("planchets_h",
    552                                               &recoup->planchets_h),
    553         GNUNET_PQ_result_spec_uint64 ("recoup_uuid",
    554                                       &serial_id),
    555         GNUNET_PQ_result_spec_end
    556       };
    557 
    558       if (GNUNET_OK !=
    559           GNUNET_PQ_extract_result (result,
    560                                     rs,
    561                                     i))
    562       {
    563         GNUNET_break (0);
    564         GNUNET_free (recoup);
    565         chc->failed = true;
    566         return;
    567       }
    568     }
    569     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
    570     tl->next = chc->head;
    571     tl->type = TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW;
    572     tl->details.recoup = recoup;
    573     tl->serial_id = serial_id;
    574     tl->coin_history_id = chc->chid;
    575     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 add_coin_recoup_refresh (void *cls,
    590                          PGresult *result,
    591                          unsigned int num_results)
    592 {
    593   struct CoinHistoryContext *chc = cls;
    594   struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
    595 
    596   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     recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupRefreshListEntry);
    603     {
    604       struct GNUNET_PQ_ResultSpec rs[] = {
    605         GNUNET_PQ_result_spec_auto_from_type ("old_coin_pub",
    606                                               &recoup->old_coin_pub),
    607         GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
    608                                               &recoup->coin_sig),
    609         GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
    610                                               &recoup->coin_blind),
    611         TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
    612                                      &recoup->value),
    613         GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
    614                                          &recoup->timestamp),
    615         GNUNET_PQ_result_spec_uint32 ("coin_index",
    616                                       &recoup->coin_index),
    617         GNUNET_PQ_result_spec_auto_from_type ("rc",
    618                                               &recoup->rc),
    619         GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
    620                                               &recoup->coin.denom_pub_hash),
    621         TALER_PQ_result_spec_denom_sig ("denom_sig",
    622                                         &recoup->coin.denom_sig),
    623         GNUNET_PQ_result_spec_uint64 ("recoup_refresh_uuid",
    624                                       &serial_id),
    625         GNUNET_PQ_result_spec_end
    626       };
    627 
    628       if (GNUNET_OK !=
    629           GNUNET_PQ_extract_result (result,
    630                                     rs,
    631                                     i))
    632       {
    633         GNUNET_break (0);
    634         GNUNET_free (recoup);
    635         chc->failed = true;
    636         return;
    637       }
    638       recoup->coin.coin_pub = *chc->coin_pub;
    639     }
    640     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
    641     tl->next = chc->head;
    642     tl->type = TALER_EXCHANGEDB_TT_RECOUP_REFRESH;
    643     tl->details.recoup_refresh = recoup;
    644     tl->serial_id = serial_id;
    645     tl->coin_history_id = chc->chid;
    646     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 add_coin_reserve_open (void *cls,
    661                        PGresult *result,
    662                        unsigned int num_results)
    663 {
    664   struct CoinHistoryContext *chc = cls;
    665   struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
    666 
    667   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     role = GNUNET_new (struct TALER_EXCHANGEDB_ReserveOpenListEntry);
    674     {
    675       struct GNUNET_PQ_ResultSpec rs[] = {
    676         GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
    677                                               &role->reserve_sig),
    678         GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
    679                                               &role->coin_sig),
    680         TALER_PQ_RESULT_SPEC_AMOUNT ("contribution",
    681                                      &role->coin_contribution),
    682         GNUNET_PQ_result_spec_allow_null (
    683           GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
    684                                                 &role->h_age_commitment),
    685           &role->no_age_commitment),
    686         GNUNET_PQ_result_spec_uint64 ("reserve_open_deposit_uuid",
    687                                       &serial_id),
    688         GNUNET_PQ_result_spec_end
    689       };
    690 
    691       if (GNUNET_OK !=
    692           GNUNET_PQ_extract_result (result,
    693                                     rs,
    694                                     i))
    695       {
    696         GNUNET_break (0);
    697         GNUNET_free (role);
    698         chc->failed = true;
    699         return;
    700       }
    701     }
    702     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
    703     tl->next = chc->head;
    704     tl->type = TALER_EXCHANGEDB_TT_RESERVE_OPEN;
    705     tl->details.reserve_open = role;
    706     tl->serial_id = serial_id;
    707     tl->coin_history_id = chc->chid;
    708     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 handle_history_entry (void *cls,
    745                       PGresult *result,
    746                       unsigned int num_results)
    747 {
    748   struct CoinHistoryContext *chc = cls;
    749   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   struct GNUNET_PQ_ResultSpec rs[] = {
    792     GNUNET_PQ_result_spec_string ("table_name",
    793                                   &table_name),
    794     GNUNET_PQ_result_spec_uint64 ("serial_id",
    795                                   &serial_id),
    796     GNUNET_PQ_result_spec_uint64 ("coin_history_serial_id",
    797                                   &chc->chid),
    798     GNUNET_PQ_result_spec_end
    799   };
    800   struct GNUNET_PQ_QueryParam params[] = {
    801     GNUNET_PQ_query_param_auto_from_type (chc->coin_pub),
    802     GNUNET_PQ_query_param_uint64 (&serial_id),
    803     GNUNET_PQ_query_param_end
    804   };
    805 
    806   for (unsigned int i = 0; i<num_results; i++)
    807   {
    808     enum GNUNET_DB_QueryStatus qs;
    809     bool found = false;
    810 
    811     if (GNUNET_OK !=
    812         GNUNET_PQ_extract_result (result,
    813                                   rs,
    814                                   i))
    815     {
    816       GNUNET_break (0);
    817       chc->failed = true;
    818       return;
    819     }
    820 
    821     for (unsigned int s = 0;
    822          NULL != work[s].statement;
    823          s++)
    824     {
    825       if (0 != strcmp (table_name,
    826                        work[s].table))
    827         continue;
    828       found = true;
    829       qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
    830                                                  work[s].statement,
    831                                                  params,
    832                                                  work[s].cb,
    833                                                  chc);
    834       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       if (0 > qs)
    841         chc->failed = true;
    842       break;
    843     }
    844     if (! found)
    845     {
    846       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    847                   "Coin history includes unsupported table `%s`\n",
    848                   table_name);
    849       chc->failed = true;
    850     }
    851     GNUNET_PQ_cleanup_result (rs);
    852     if (chc->failed)
    853       break;
    854   }
    855 }
    856 
    857 
    858 enum GNUNET_DB_QueryStatus
    859 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   struct GNUNET_PQ_QueryParam params[] = {
    871     GNUNET_PQ_query_param_auto_from_type (coin_pub),
    872     GNUNET_PQ_query_param_end
    873   };
    874   struct GNUNET_PQ_QueryParam lparams[] = {
    875     GNUNET_PQ_query_param_auto_from_type (coin_pub),
    876     GNUNET_PQ_query_param_uint64 (&start_off),
    877     GNUNET_PQ_query_param_end
    878   };
    879   struct CoinHistoryContext chc = {
    880     .head = NULL,
    881     .coin_pub = coin_pub,
    882     .pg = pg
    883   };
    884 
    885   *tlp = NULL;
    886   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    887               "Getting transactions for coin %s\n",
    888               TALER_B2S (coin_pub));
    889   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   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   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   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   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   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   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   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   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   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   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   for (unsigned int i = 0; i<RETRIES; i++)
   1117   {
   1118     enum GNUNET_DB_QueryStatus qs;
   1119     uint64_t end;
   1120     struct GNUNET_PQ_ResultSpec rs[] = {
   1121       GNUNET_PQ_result_spec_uint64 ("coin_history_serial_id",
   1122                                     &end),
   1123       GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
   1124                                             h_denom_pub),
   1125       TALER_PQ_RESULT_SPEC_AMOUNT ("remaining",
   1126                                    balance),
   1127       GNUNET_PQ_result_spec_end
   1128     };
   1129 
   1130     if (begin_transaction)
   1131     {
   1132       if (GNUNET_OK !=
   1133           TALER_EXCHANGEDB_start_read_committed (pg,
   1134                                                  "get-coin-transactions"))
   1135       {
   1136         GNUNET_break (0);
   1137         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     qs = GNUNET_PQ_eval_prepared_singleton_select (
   1143       pg->conn,
   1144       "get_coin_transactions_history_etag_balance",
   1145       params,
   1146       rs);
   1147     switch (qs)
   1148     {
   1149     case GNUNET_DB_STATUS_HARD_ERROR:
   1150       if (begin_transaction)
   1151         TALER_EXCHANGEDB_rollback (pg);
   1152       return qs;
   1153     case GNUNET_DB_STATUS_SOFT_ERROR:
   1154       if (begin_transaction)
   1155         TALER_EXCHANGEDB_rollback (pg);
   1156       continue;
   1157     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
   1158       if (begin_transaction)
   1159         TALER_EXCHANGEDB_rollback (pg);
   1160       return qs;
   1161     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
   1162       *etag_out = end;
   1163       if (end == etag_in)
   1164       {
   1165         /* client is up-to-date, nothing more to do */
   1166         if (begin_transaction)
   1167           TALER_EXCHANGEDB_rollback (pg);
   1168         return qs;
   1169       }
   1170     }
   1171     /* We indeed need to iterate over the history */
   1172     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     qs = GNUNET_PQ_eval_prepared_multi_select (
   1178       pg->conn,
   1179       "get_coin_transactions_history",
   1180       lparams,
   1181       &handle_history_entry,
   1182       &chc);
   1183     switch (qs)
   1184     {
   1185     case GNUNET_DB_STATUS_HARD_ERROR:
   1186       if (begin_transaction)
   1187         TALER_EXCHANGEDB_rollback (pg);
   1188       return qs;
   1189     case GNUNET_DB_STATUS_SOFT_ERROR:
   1190       if (begin_transaction)
   1191         TALER_EXCHANGEDB_rollback (pg);
   1192       TALER_EXCHANGEDB_free_coin_transaction_list (chc.head);
   1193       chc.head = NULL;
   1194       continue;
   1195     default:
   1196       break;
   1197     }
   1198     if (chc.failed)
   1199     {
   1200       if (begin_transaction)
   1201         TALER_EXCHANGEDB_rollback (pg);
   1202       TALER_EXCHANGEDB_free_coin_transaction_list (chc.head);
   1203       return GNUNET_DB_STATUS_SOFT_ERROR;
   1204     }
   1205     if (! begin_transaction)
   1206     {
   1207       *tlp = chc.head;
   1208       return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
   1209     }
   1210     qs = TALER_EXCHANGEDB_commit (pg);
   1211     switch (qs)
   1212     {
   1213     case GNUNET_DB_STATUS_HARD_ERROR:
   1214       TALER_EXCHANGEDB_free_coin_transaction_list (chc.head);
   1215       chc.head = NULL;
   1216       return qs;
   1217     case GNUNET_DB_STATUS_SOFT_ERROR:
   1218       TALER_EXCHANGEDB_free_coin_transaction_list (chc.head);
   1219       chc.head = NULL;
   1220       continue;
   1221     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
   1222     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
   1223       *tlp = chc.head;
   1224       return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
   1225     }
   1226   }
   1227   return GNUNET_DB_STATUS_SOFT_ERROR;
   1228 }