exchange

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

pg_get_coin_transactions.c (39606B)


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