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


      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 GNUNET_PQ_ResultSpec rs[] = {
    262         GNUNET_PQ_result_spec_auto_from_type ("rc",
    263                                               &melt->rc),
    264         /* oldcoin_index not needed */
    265         GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
    266                                               &melt->h_denom_pub),
    267         GNUNET_PQ_result_spec_auto_from_type ("old_coin_sig",
    268                                               &melt->coin_sig),
    269         GNUNET_PQ_result_spec_auto_from_type ("refresh_seed",
    270                                               &melt->refresh_seed),
    271         GNUNET_PQ_result_spec_allow_null (
    272           GNUNET_PQ_result_spec_auto_from_type ("blinding_seed",
    273                                                 &melt->blinding_seed),
    274           &melt->no_blinding_seed),
    275         TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
    276                                      &melt->amount_with_fee),
    277         TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh",
    278                                      &melt->melt_fee),
    279         GNUNET_PQ_result_spec_allow_null (
    280           GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
    281                                                 &melt->h_age_commitment),
    282           &melt->no_age_commitment),
    283         GNUNET_PQ_result_spec_uint64 ("refresh_id",
    284                                       &serial_id),
    285         GNUNET_PQ_result_spec_end
    286       };
    287 
    288       if (GNUNET_OK !=
    289           GNUNET_PQ_extract_result (result,
    290                                     rs,
    291                                     i))
    292       {
    293         GNUNET_break (0);
    294         GNUNET_free (melt);
    295         chc->failed = true;
    296         return;
    297       }
    298     }
    299     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
    300     tl->next = chc->head;
    301     tl->type = TALER_EXCHANGEDB_TT_MELT;
    302     tl->details.melt = melt;
    303     tl->serial_id = serial_id;
    304     tl->coin_history_id = chc->chid;
    305     chc->head = tl;
    306   }
    307 }
    308 
    309 
    310 /**
    311  * Function to be called with the results of a SELECT statement
    312  * that has returned @a num_results results.
    313  *
    314  * @param cls closure of type `struct CoinHistoryContext`
    315  * @param result the postgres result
    316  * @param num_results the number of results in @a result
    317  */
    318 static void
    319 add_coin_refund (void *cls,
    320                  PGresult *result,
    321                  unsigned int num_results)
    322 {
    323   struct CoinHistoryContext *chc = cls;
    324   struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
    325 
    326   for (unsigned int i = 0; i<num_results; i++)
    327   {
    328     struct TALER_EXCHANGEDB_RefundListEntry *refund;
    329     struct TALER_EXCHANGEDB_TransactionList *tl;
    330     uint64_t serial_id;
    331 
    332     refund = GNUNET_new (struct TALER_EXCHANGEDB_RefundListEntry);
    333     {
    334       struct GNUNET_PQ_ResultSpec rs[] = {
    335         GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
    336                                               &refund->merchant_pub),
    337         GNUNET_PQ_result_spec_auto_from_type ("merchant_sig",
    338                                               &refund->merchant_sig),
    339         GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
    340                                               &refund->h_contract_terms),
    341         GNUNET_PQ_result_spec_uint64 ("rtransaction_id",
    342                                       &refund->rtransaction_id),
    343         TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
    344                                      &refund->refund_amount),
    345         TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
    346                                      &refund->refund_fee),
    347         GNUNET_PQ_result_spec_uint64 ("refund_serial_id",
    348                                       &serial_id),
    349         GNUNET_PQ_result_spec_end
    350       };
    351 
    352       if (GNUNET_OK !=
    353           GNUNET_PQ_extract_result (result,
    354                                     rs,
    355                                     i))
    356       {
    357         GNUNET_break (0);
    358         GNUNET_free (refund);
    359         chc->failed = true;
    360         return;
    361       }
    362     }
    363     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
    364     tl->next = chc->head;
    365     tl->type = TALER_EXCHANGEDB_TT_REFUND;
    366     tl->details.refund = refund;
    367     tl->serial_id = serial_id;
    368     tl->coin_history_id = chc->chid;
    369     chc->head = tl;
    370   }
    371 }
    372 
    373 
    374 /**
    375  * Function to be called with the results of a SELECT statement
    376  * that has returned @a num_results results.
    377  *
    378  * @param cls closure of type `struct CoinHistoryContext`
    379  * @param result the postgres result
    380  * @param num_results the number of results in @a result
    381  */
    382 static void
    383 add_coin_purse_decision (void *cls,
    384                          PGresult *result,
    385                          unsigned int num_results)
    386 {
    387   struct CoinHistoryContext *chc = cls;
    388   struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
    389 
    390   for (unsigned int i = 0; i<num_results; i++)
    391   {
    392     struct TALER_EXCHANGEDB_PurseRefundListEntry *prefund;
    393     struct TALER_EXCHANGEDB_TransactionList *tl;
    394     uint64_t serial_id;
    395 
    396     prefund = GNUNET_new (struct TALER_EXCHANGEDB_PurseRefundListEntry);
    397     {
    398       struct GNUNET_PQ_ResultSpec rs[] = {
    399         GNUNET_PQ_result_spec_auto_from_type ("purse_pub",
    400                                               &prefund->purse_pub),
    401         TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
    402                                      &prefund->refund_amount),
    403         TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
    404                                      &prefund->refund_fee),
    405         GNUNET_PQ_result_spec_uint64 ("purse_decision_serial_id",
    406                                       &serial_id),
    407         GNUNET_PQ_result_spec_end
    408       };
    409 
    410       if (GNUNET_OK !=
    411           GNUNET_PQ_extract_result (result,
    412                                     rs,
    413                                     i))
    414       {
    415         GNUNET_break (0);
    416         GNUNET_free (prefund);
    417         chc->failed = true;
    418         return;
    419       }
    420     }
    421     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
    422     tl->next = chc->head;
    423     tl->type = TALER_EXCHANGEDB_TT_PURSE_REFUND;
    424     tl->details.purse_refund = prefund;
    425     tl->serial_id = serial_id;
    426     tl->coin_history_id = chc->chid;
    427     chc->head = tl;
    428   }
    429 }
    430 
    431 
    432 /**
    433  * Function to be called with the results of a SELECT statement
    434  * that has returned @a num_results results.
    435  *
    436  * @param cls closure of type `struct CoinHistoryContext`
    437  * @param result the postgres result
    438  * @param num_results the number of results in @a result
    439  */
    440 static void
    441 add_old_coin_recoup (void *cls,
    442                      PGresult *result,
    443                      unsigned int num_results)
    444 {
    445   struct CoinHistoryContext *chc = cls;
    446   struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
    447 
    448   for (unsigned int i = 0; i<num_results; i++)
    449   {
    450     struct TALER_EXCHANGEDB_RecoupRefreshListEntry *recoup;
    451     struct TALER_EXCHANGEDB_TransactionList *tl;
    452     uint64_t serial_id;
    453 
    454     recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupRefreshListEntry);
    455     {
    456       struct GNUNET_PQ_ResultSpec rs[] = {
    457         GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
    458                                               &recoup->coin.coin_pub),
    459         GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
    460                                               &recoup->coin_sig),
    461         GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
    462                                               &recoup->coin_blind),
    463         TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
    464                                      &recoup->value),
    465         GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
    466                                          &recoup->timestamp),
    467         GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
    468                                               &recoup->coin.denom_pub_hash),
    469         TALER_PQ_result_spec_denom_sig ("denom_sig",
    470                                         &recoup->coin.denom_sig),
    471         GNUNET_PQ_result_spec_uint64 ("recoup_refresh_uuid",
    472                                       &serial_id),
    473         GNUNET_PQ_result_spec_end
    474       };
    475 
    476       if (GNUNET_OK !=
    477           GNUNET_PQ_extract_result (result,
    478                                     rs,
    479                                     i))
    480       {
    481         GNUNET_break (0);
    482         GNUNET_free (recoup);
    483         chc->failed = true;
    484         return;
    485       }
    486       recoup->old_coin_pub = *chc->coin_pub;
    487     }
    488     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
    489     tl->next = chc->head;
    490     tl->type = TALER_EXCHANGEDB_TT_RECOUP_REFRESH_RECEIVER;
    491     tl->details.old_coin_recoup = recoup;
    492     tl->serial_id = serial_id;
    493     tl->coin_history_id = chc->chid;
    494     chc->head = tl;
    495   }
    496 }
    497 
    498 
    499 /**
    500  * Function to be called with the results of a SELECT statement
    501  * that has returned @a num_results results.
    502  *
    503  * @param cls closure of type `struct CoinHistoryContext`
    504  * @param result the postgres result
    505  * @param num_results the number of results in @a result
    506  */
    507 static void
    508 add_coin_recoup (void *cls,
    509                  PGresult *result,
    510                  unsigned int num_results)
    511 {
    512   struct CoinHistoryContext *chc = cls;
    513   struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
    514 
    515   for (unsigned int i = 0; i<num_results; i++)
    516   {
    517     struct TALER_EXCHANGEDB_RecoupListEntry *recoup;
    518     struct TALER_EXCHANGEDB_TransactionList *tl;
    519     uint64_t serial_id;
    520 
    521     recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupListEntry);
    522     {
    523       struct GNUNET_PQ_ResultSpec rs[] = {
    524         GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
    525                                               &recoup->reserve_pub),
    526         GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
    527                                               &recoup->coin_sig),
    528         GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
    529                                               &recoup->h_denom_pub),
    530         GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
    531                                               &recoup->coin_blind),
    532         TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
    533                                      &recoup->value),
    534         GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
    535                                          &recoup->timestamp),
    536         GNUNET_PQ_result_spec_uint64 ("recoup_uuid",
    537                                       &serial_id),
    538         GNUNET_PQ_result_spec_end
    539       };
    540 
    541       if (GNUNET_OK !=
    542           GNUNET_PQ_extract_result (result,
    543                                     rs,
    544                                     i))
    545       {
    546         GNUNET_break (0);
    547         GNUNET_free (recoup);
    548         chc->failed = true;
    549         return;
    550       }
    551     }
    552     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
    553     tl->next = chc->head;
    554     tl->type = TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW;
    555     tl->details.recoup = recoup;
    556     tl->serial_id = serial_id;
    557     tl->coin_history_id = chc->chid;
    558     chc->head = tl;
    559   }
    560 }
    561 
    562 
    563 /**
    564  * Function to be called with the results of a SELECT statement
    565  * that has returned @a num_results results.
    566  *
    567  * @param cls closure of type `struct CoinHistoryContext`
    568  * @param result the postgres result
    569  * @param num_results the number of results in @a result
    570  */
    571 static void
    572 add_coin_recoup_refresh (void *cls,
    573                          PGresult *result,
    574                          unsigned int num_results)
    575 {
    576   struct CoinHistoryContext *chc = cls;
    577   struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
    578 
    579   for (unsigned int i = 0; i<num_results; i++)
    580   {
    581     struct TALER_EXCHANGEDB_RecoupRefreshListEntry *recoup;
    582     struct TALER_EXCHANGEDB_TransactionList *tl;
    583     uint64_t serial_id;
    584 
    585     recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupRefreshListEntry);
    586     {
    587       struct GNUNET_PQ_ResultSpec rs[] = {
    588         GNUNET_PQ_result_spec_auto_from_type ("old_coin_pub",
    589                                               &recoup->old_coin_pub),
    590         GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
    591                                               &recoup->coin_sig),
    592         GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
    593                                               &recoup->coin_blind),
    594         TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
    595                                      &recoup->value),
    596         GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
    597                                          &recoup->timestamp),
    598         GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
    599                                               &recoup->coin.denom_pub_hash),
    600         TALER_PQ_result_spec_denom_sig ("denom_sig",
    601                                         &recoup->coin.denom_sig),
    602         GNUNET_PQ_result_spec_uint64 ("recoup_refresh_uuid",
    603                                       &serial_id),
    604         GNUNET_PQ_result_spec_end
    605       };
    606 
    607       if (GNUNET_OK !=
    608           GNUNET_PQ_extract_result (result,
    609                                     rs,
    610                                     i))
    611       {
    612         GNUNET_break (0);
    613         GNUNET_free (recoup);
    614         chc->failed = true;
    615         return;
    616       }
    617       recoup->coin.coin_pub = *chc->coin_pub;
    618     }
    619     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
    620     tl->next = chc->head;
    621     tl->type = TALER_EXCHANGEDB_TT_RECOUP_REFRESH;
    622     tl->details.recoup_refresh = recoup;
    623     tl->serial_id = serial_id;
    624     tl->coin_history_id = chc->chid;
    625     chc->head = tl;
    626   }
    627 }
    628 
    629 
    630 /**
    631  * Function to be called with the results of a SELECT statement
    632  * that has returned @a num_results results.
    633  *
    634  * @param cls closure of type `struct CoinHistoryContext`
    635  * @param result the postgres result
    636  * @param num_results the number of results in @a result
    637  */
    638 static void
    639 add_coin_reserve_open (void *cls,
    640                        PGresult *result,
    641                        unsigned int num_results)
    642 {
    643   struct CoinHistoryContext *chc = cls;
    644   struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
    645 
    646   for (unsigned int i = 0; i<num_results; i++)
    647   {
    648     struct TALER_EXCHANGEDB_ReserveOpenListEntry *role;
    649     struct TALER_EXCHANGEDB_TransactionList *tl;
    650     uint64_t serial_id;
    651 
    652     role = GNUNET_new (struct TALER_EXCHANGEDB_ReserveOpenListEntry);
    653     {
    654       struct GNUNET_PQ_ResultSpec rs[] = {
    655         GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
    656                                               &role->reserve_sig),
    657         GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
    658                                               &role->coin_sig),
    659         TALER_PQ_RESULT_SPEC_AMOUNT ("contribution",
    660                                      &role->coin_contribution),
    661         GNUNET_PQ_result_spec_uint64 ("reserve_open_deposit_uuid",
    662                                       &serial_id),
    663         GNUNET_PQ_result_spec_end
    664       };
    665 
    666       if (GNUNET_OK !=
    667           GNUNET_PQ_extract_result (result,
    668                                     rs,
    669                                     i))
    670       {
    671         GNUNET_break (0);
    672         GNUNET_free (role);
    673         chc->failed = true;
    674         return;
    675       }
    676     }
    677     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
    678     tl->next = chc->head;
    679     tl->type = TALER_EXCHANGEDB_TT_RESERVE_OPEN;
    680     tl->details.reserve_open = role;
    681     tl->serial_id = serial_id;
    682     tl->coin_history_id = chc->chid;
    683     chc->head = tl;
    684   }
    685 }
    686 
    687 
    688 /**
    689  * Work we need to do.
    690  */
    691 struct Work
    692 {
    693   /**
    694    * Name of the table.
    695    */
    696   const char *table;
    697 
    698   /**
    699    * SQL prepared statement name.
    700    */
    701   const char *statement;
    702 
    703   /**
    704    * Function to call to handle the result(s).
    705    */
    706   GNUNET_PQ_PostgresResultHandler cb;
    707 };
    708 
    709 
    710 /**
    711  * We found a coin history entry. Lookup details
    712  * from the respective table and store in @a cls.
    713  *
    714  * @param[in,out] cls a `struct CoinHistoryContext`
    715  * @param result a coin history entry result set
    716  * @param num_results total number of results in @a results
    717  */
    718 static void
    719 handle_history_entry (void *cls,
    720                       PGresult *result,
    721                       unsigned int num_results)
    722 {
    723   struct CoinHistoryContext *chc = cls;
    724   struct TALER_EXCHANGEDB_PostgresContext *pg = chc->pg;
    725   static const struct Work work[] = {
    726     [TALER_EXCHANGEDB_TT_DEPOSIT] =
    727     { "coin_deposits",
    728       "get_deposit_with_coin_pub",
    729       &add_coin_deposit },
    730     [TALER_EXCHANGEDB_TT_MELT] =
    731     { "refresh",
    732       "get_refresh_by_coin",
    733       &add_coin_melt },
    734     [TALER_EXCHANGEDB_TT_PURSE_DEPOSIT] =
    735     { "purse_deposits",
    736       "get_purse_deposit_by_coin_pub",
    737       &add_coin_purse_deposit },
    738     [TALER_EXCHANGEDB_TT_PURSE_REFUND] =
    739     { "purse_decision",
    740       "get_purse_decision_by_coin_pub",
    741       &add_coin_purse_decision },
    742     [TALER_EXCHANGEDB_TT_REFUND] =
    743     { "refunds",
    744       "get_refunds_by_coin",
    745       &add_coin_refund },
    746     [TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW] =
    747     { "recoup",
    748       "recoup_by_coin",
    749       &add_coin_recoup },
    750     [TALER_EXCHANGEDB_TT_RECOUP_REFRESH] =
    751     { "recoup_refresh::NEW",
    752       "recoup_by_refreshed_coin",
    753       &add_coin_recoup_refresh },
    754     [TALER_EXCHANGEDB_TT_RECOUP_REFRESH_RECEIVER] =
    755     { "recoup_refresh::OLD",
    756       "recoup_by_old_coin",
    757       &add_old_coin_recoup },
    758     [TALER_EXCHANGEDB_TT_RESERVE_OPEN] =
    759     { "reserves_open_deposits",
    760       "reserve_open_by_coin",
    761       &add_coin_reserve_open },
    762     { NULL, NULL, NULL }
    763   };
    764   char *table_name;
    765   uint64_t serial_id;
    766   struct GNUNET_PQ_ResultSpec rs[] = {
    767     GNUNET_PQ_result_spec_string ("table_name",
    768                                   &table_name),
    769     GNUNET_PQ_result_spec_uint64 ("serial_id",
    770                                   &serial_id),
    771     GNUNET_PQ_result_spec_uint64 ("coin_history_serial_id",
    772                                   &chc->chid),
    773     GNUNET_PQ_result_spec_end
    774   };
    775   struct GNUNET_PQ_QueryParam params[] = {
    776     GNUNET_PQ_query_param_auto_from_type (chc->coin_pub),
    777     GNUNET_PQ_query_param_uint64 (&serial_id),
    778     GNUNET_PQ_query_param_end
    779   };
    780 
    781   for (unsigned int i = 0; i<num_results; i++)
    782   {
    783     enum GNUNET_DB_QueryStatus qs;
    784     bool found = false;
    785 
    786     if (GNUNET_OK !=
    787         GNUNET_PQ_extract_result (result,
    788                                   rs,
    789                                   i))
    790     {
    791       GNUNET_break (0);
    792       chc->failed = true;
    793       return;
    794     }
    795 
    796     for (unsigned int s = 0;
    797          NULL != work[s].statement;
    798          s++)
    799     {
    800       if (0 != strcmp (table_name,
    801                        work[s].table))
    802         continue;
    803       found = true;
    804       qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
    805                                                  work[s].statement,
    806                                                  params,
    807                                                  work[s].cb,
    808                                                  chc);
    809       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    810                   "Coin %s had %d transactions at %llu in table %s\n",
    811                   TALER_B2S (chc->coin_pub),
    812                   (int) qs,
    813                   (unsigned long long) serial_id,
    814                   table_name);
    815       if (0 > qs)
    816         chc->failed = true;
    817       break;
    818     }
    819     if (! found)
    820     {
    821       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    822                   "Coin history includes unsupported table `%s`\n",
    823                   table_name);
    824       chc->failed = true;
    825     }
    826     GNUNET_PQ_cleanup_result (rs);
    827     if (chc->failed)
    828       break;
    829   }
    830 }
    831 
    832 
    833 enum GNUNET_DB_QueryStatus
    834 TALER_EXCHANGEDB_get_coin_transactions (
    835   struct TALER_EXCHANGEDB_PostgresContext *pg,
    836   bool begin_transaction,
    837   const struct TALER_CoinSpendPublicKeyP *coin_pub,
    838   uint64_t start_off,
    839   uint64_t etag_in,
    840   uint64_t *etag_out,
    841   struct TALER_Amount *balance,
    842   struct TALER_DenominationHashP *h_denom_pub,
    843   struct TALER_EXCHANGEDB_TransactionList **tlp)
    844 {
    845   struct GNUNET_PQ_QueryParam params[] = {
    846     GNUNET_PQ_query_param_auto_from_type (coin_pub),
    847     GNUNET_PQ_query_param_end
    848   };
    849   struct GNUNET_PQ_QueryParam lparams[] = {
    850     GNUNET_PQ_query_param_auto_from_type (coin_pub),
    851     GNUNET_PQ_query_param_uint64 (&start_off),
    852     GNUNET_PQ_query_param_end
    853   };
    854   struct CoinHistoryContext chc = {
    855     .head = NULL,
    856     .coin_pub = coin_pub,
    857     .pg = pg
    858   };
    859 
    860   *tlp = NULL;
    861   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    862               "Getting transactions for coin %s\n",
    863               TALER_B2S (coin_pub));
    864   PREPARE (pg,
    865            "get_coin_history_etag_balance",
    866            "SELECT"
    867            " ch.coin_history_serial_id"
    868            ",kc.remaining"
    869            ",denom.denom_pub_hash"
    870            " FROM coin_history ch"
    871            " JOIN known_coins kc"
    872            "   USING (coin_pub)"
    873            " JOIN denominations denom"
    874            "   USING (denominations_serial)"
    875            " WHERE coin_pub=$1"
    876            " ORDER BY coin_history_serial_id DESC"
    877            " LIMIT 1;");
    878   PREPARE (pg,
    879            "get_coin_history",
    880            "SELECT"
    881            " table_name"
    882            ",serial_id"
    883            ",coin_history_serial_id"
    884            " FROM coin_history"
    885            " WHERE coin_pub=$1"
    886            "   AND coin_history_serial_id > $2"
    887            " ORDER BY coin_history_serial_id DESC;");
    888   PREPARE (pg,
    889            "get_deposit_with_coin_pub",
    890            "SELECT"
    891            " cdep.amount_with_fee"
    892            ",denoms.fee_deposit"
    893            ",denoms.denom_pub_hash"
    894            ",kc.age_commitment_hash"
    895            ",bdep.wallet_timestamp"
    896            ",bdep.refund_deadline"
    897            ",bdep.wire_deadline"
    898            ",bdep.merchant_pub"
    899            ",bdep.h_contract_terms"
    900            ",bdep.wallet_data_hash"
    901            ",bdep.wire_salt"
    902            ",wt.payto_uri"
    903            ",cdep.coin_sig"
    904            ",cdep.coin_deposit_serial_id"
    905            ",bdep.done"
    906            " FROM coin_deposits cdep"
    907            " JOIN batch_deposits bdep"
    908            "   USING (batch_deposit_serial_id)"
    909            " JOIN wire_targets wt"
    910            "   USING (wire_target_h_payto)"
    911            " JOIN known_coins kc"
    912            "   ON (kc.coin_pub = cdep.coin_pub)"
    913            " JOIN denominations denoms"
    914            "   USING (denominations_serial)"
    915            " WHERE cdep.coin_pub=$1"
    916            "   AND cdep.coin_deposit_serial_id=$2;");
    917   PREPARE (pg,
    918            "get_refresh_by_coin",
    919            "SELECT"
    920            " rc"
    921            ",refresh_seed"
    922            ",blinding_seed"
    923            ",old_coin_sig"
    924            ",amount_with_fee"
    925            ",denoms.denom_pub_hash"
    926            ",denoms.fee_refresh"
    927            ",kc.age_commitment_hash"
    928            ",refresh_id"
    929            " FROM refresh"
    930            " JOIN known_coins kc"
    931            "   ON (refresh.old_coin_pub = kc.coin_pub)"
    932            " JOIN denominations denoms"
    933            "   USING (denominations_serial)"
    934            " WHERE old_coin_pub=$1"
    935            "   AND refresh_id=$2;");
    936   PREPARE (pg,
    937            "get_purse_deposit_by_coin_pub",
    938            "SELECT"
    939            " partner_base_url"
    940            ",pd.amount_with_fee"
    941            ",denoms.fee_deposit"
    942            ",denoms.denom_pub_hash"
    943            ",pd.purse_pub"
    944            ",kc.age_commitment_hash"
    945            ",pd.coin_sig"
    946            ",pd.purse_deposit_serial_id"
    947            ",pdes.refunded"
    948            " FROM purse_deposits pd"
    949            " LEFT JOIN partners"
    950            "   USING (partner_serial_id)"
    951            " JOIN purse_requests pr"
    952            "   USING (purse_pub)"
    953            " LEFT JOIN purse_decision pdes"
    954            "   USING (purse_pub)"
    955            " JOIN known_coins kc"
    956            "   ON (pd.coin_pub = kc.coin_pub)"
    957            " JOIN denominations denoms"
    958            "   USING (denominations_serial)"
    959            " WHERE pd.purse_deposit_serial_id=$2"
    960            "   AND pd.coin_pub=$1;");
    961   PREPARE (pg,
    962            "get_purse_decision_by_coin_pub",
    963            "SELECT"
    964            " pdes.purse_pub"
    965            ",pd.amount_with_fee"
    966            ",denom.fee_refund"
    967            ",pdes.purse_decision_serial_id"
    968            " FROM purse_decision pdes"
    969            " JOIN purse_deposits pd"
    970            "   USING (purse_pub)"
    971            " JOIN known_coins kc"
    972            "   ON (pd.coin_pub = kc.coin_pub)"
    973            " JOIN denominations denom"
    974            "   USING (denominations_serial)"
    975            " WHERE pd.coin_pub=$1"
    976            "   AND pdes.purse_decision_serial_id=$2"
    977            "   AND pdes.refunded;");
    978   PREPARE (pg,
    979            "get_refunds_by_coin",
    980            "SELECT"
    981            " bdep.merchant_pub"
    982            ",ref.merchant_sig"
    983            ",bdep.h_contract_terms"
    984            ",ref.rtransaction_id"
    985            ",ref.amount_with_fee"
    986            ",denom.fee_refund"
    987            ",ref.refund_serial_id"
    988            " FROM refunds ref"
    989            " JOIN coin_deposits cdep"
    990            "   ON (ref.coin_pub = cdep.coin_pub AND ref.batch_deposit_serial_id = cdep.batch_deposit_serial_id)"
    991            " JOIN batch_deposits bdep"
    992            "   ON (ref.batch_deposit_serial_id = bdep.batch_deposit_serial_id)"
    993            " JOIN known_coins kc"
    994            "   ON (ref.coin_pub = kc.coin_pub)"
    995            " JOIN denominations denom"
    996            "   USING (denominations_serial)"
    997            " WHERE ref.coin_pub=$1"
    998            "   AND ref.refund_serial_id=$2;");
    999   PREPARE (pg,
   1000            "recoup_by_old_coin",
   1001            "SELECT"
   1002            " coins.coin_pub"
   1003            ",rr.coin_sig"
   1004            ",rr.coin_blind"
   1005            ",rr.amount"
   1006            ",rr.recoup_timestamp"
   1007            ",denoms.denom_pub_hash"
   1008            ",coins.denom_sig"
   1009            ",rr.recoup_refresh_uuid"
   1010            " FROM recoup_refresh rr"
   1011            " JOIN known_coins coins"
   1012            "   USING (coin_pub)"
   1013            " JOIN denominations denoms"
   1014            "   USING (denominations_serial)"
   1015            " WHERE recoup_refresh_uuid=$2"
   1016            "   AND refresh_id IN"
   1017            "   (SELECT refresh_id"
   1018            "    FROM refresh"
   1019            "    WHERE refresh.old_coin_pub=$1);");
   1020   PREPARE (pg,
   1021            "recoup_by_coin",
   1022            "SELECT"
   1023            " res.reserve_pub"
   1024            ",denoms.denom_pub_hash"
   1025            ",rcp.coin_sig"
   1026            ",rcp.coin_blind"
   1027            ",rcp.amount"
   1028            ",rcp.recoup_timestamp"
   1029            ",rcp.recoup_uuid"
   1030            " FROM recoup rcp"
   1031            " JOIN withdraw ro"
   1032            "   USING (withdraw_id)"
   1033            " JOIN reserves res"
   1034            "   USING (reserve_pub)"
   1035            " JOIN known_coins coins"
   1036            "   USING (coin_pub)"
   1037            " JOIN denominations denoms"
   1038            "   ON (denoms.denominations_serial = coins.denominations_serial)"
   1039            " WHERE rcp.recoup_uuid=$2"
   1040            "   AND coins.coin_pub=$1;");
   1041   /* Used to obtain recoup transactions
   1042      for a refreshed coin */
   1043   PREPARE (pg,
   1044            "recoup_by_refreshed_coin",
   1045            "SELECT"
   1046            " old_coins.coin_pub AS old_coin_pub"
   1047            ",rr.coin_sig"
   1048            ",rr.coin_blind"
   1049            ",rr.amount"
   1050            ",rr.recoup_timestamp"
   1051            ",denoms.denom_pub_hash"
   1052            ",coins.denom_sig"
   1053            ",recoup_refresh_uuid"
   1054            " FROM recoup_refresh rr"
   1055            "    JOIN refresh rfc"
   1056            "      ON (rr.refresh_id = rfc.refresh_id)"
   1057            "    JOIN known_coins old_coins"
   1058            "      ON (rfc.old_coin_pub = old_coins.coin_pub)"
   1059            "    JOIN known_coins coins"
   1060            "      ON (rr.coin_pub = coins.coin_pub)"
   1061            "    JOIN denominations denoms"
   1062            "      ON (denoms.denominations_serial = coins.denominations_serial)"
   1063            " WHERE rr.recoup_refresh_uuid=$2"
   1064            "   AND coins.coin_pub=$1;");
   1065   PREPARE (pg,
   1066            "reserve_open_by_coin",
   1067            "SELECT"
   1068            " reserve_open_deposit_uuid"
   1069            ",coin_sig"
   1070            ",reserve_sig"
   1071            ",contribution"
   1072            " FROM reserves_open_deposits"
   1073            " WHERE coin_pub=$1"
   1074            "   AND reserve_open_deposit_uuid=$2;");
   1075   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1076               "  --- landed here 1\n");
   1077   for (unsigned int i = 0; i<RETRIES; i++)
   1078   {
   1079     enum GNUNET_DB_QueryStatus qs;
   1080     uint64_t end;
   1081     struct GNUNET_PQ_ResultSpec rs[] = {
   1082       GNUNET_PQ_result_spec_uint64 ("coin_history_serial_id",
   1083                                     &end),
   1084       GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
   1085                                             h_denom_pub),
   1086       TALER_PQ_RESULT_SPEC_AMOUNT ("remaining",
   1087                                    balance),
   1088       GNUNET_PQ_result_spec_end
   1089     };
   1090 
   1091     if (begin_transaction)
   1092     {
   1093       if (GNUNET_OK !=
   1094           TALER_TALER_EXCHANGEDB_start_read_committed (pg,
   1095                                                        "get-coin-transactions"))
   1096       {
   1097         GNUNET_break (0);
   1098         return GNUNET_DB_STATUS_HARD_ERROR;
   1099       }
   1100     }
   1101     /* First only check the last item, to see if
   1102        we even need to iterate */
   1103     qs = GNUNET_PQ_eval_prepared_singleton_select (
   1104       pg->conn,
   1105       "get_coin_history_etag_balance",
   1106       params,
   1107       rs);
   1108     switch (qs)
   1109     {
   1110     case GNUNET_DB_STATUS_HARD_ERROR:
   1111       if (begin_transaction)
   1112         TALER_EXCHANGEDB_rollback (pg);
   1113       return qs;
   1114     case GNUNET_DB_STATUS_SOFT_ERROR:
   1115       if (begin_transaction)
   1116         TALER_EXCHANGEDB_rollback (pg);
   1117       continue;
   1118     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
   1119       if (begin_transaction)
   1120         TALER_EXCHANGEDB_rollback (pg);
   1121       return qs;
   1122     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
   1123       *etag_out = end;
   1124       if (end == etag_in)
   1125         return qs;
   1126     }
   1127     /* We indeed need to iterate over the history */
   1128     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1129                 "Current ETag for coin %s is %llu\n",
   1130                 TALER_B2S (coin_pub),
   1131                 (unsigned long long) end);
   1132 
   1133     qs = GNUNET_PQ_eval_prepared_multi_select (
   1134       pg->conn,
   1135       "get_coin_history",
   1136       lparams,
   1137       &handle_history_entry,
   1138       &chc);
   1139     switch (qs)
   1140     {
   1141     case GNUNET_DB_STATUS_HARD_ERROR:
   1142       if (begin_transaction)
   1143         TALER_EXCHANGEDB_rollback (pg);
   1144       return qs;
   1145     case GNUNET_DB_STATUS_SOFT_ERROR:
   1146       if (begin_transaction)
   1147         TALER_EXCHANGEDB_rollback (pg);
   1148       continue;
   1149     default:
   1150       break;
   1151     }
   1152     if (chc.failed)
   1153     {
   1154       if (begin_transaction)
   1155         TALER_EXCHANGEDB_rollback (pg);
   1156       TALER_EXCHANGEDB_free_coin_transaction_list (chc.head);
   1157       return GNUNET_DB_STATUS_SOFT_ERROR;
   1158     }
   1159     if (! begin_transaction)
   1160     {
   1161       *tlp = chc.head;
   1162       return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
   1163     }
   1164     qs = TALER_EXCHANGEDB_commit (pg);
   1165     switch (qs)
   1166     {
   1167     case GNUNET_DB_STATUS_HARD_ERROR:
   1168       TALER_EXCHANGEDB_free_coin_transaction_list (chc.head);
   1169       chc.head = NULL;
   1170       return qs;
   1171     case GNUNET_DB_STATUS_SOFT_ERROR:
   1172       TALER_EXCHANGEDB_free_coin_transaction_list (chc.head);
   1173       chc.head = NULL;
   1174       continue;
   1175     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
   1176     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
   1177       *tlp = chc.head;
   1178       return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
   1179     }
   1180   }
   1181   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1182               "  --- landed here 2\n");
   1183   return GNUNET_DB_STATUS_SOFT_ERROR;
   1184 }