exchange

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

get_reserve_history.c (30232B)


      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_reserve_history.c
     18  * @brief Obtain (parts of) the history of a reserve.
     19  * @author Christian Grothoff
     20  */
     21 #include "taler/taler_error_codes.h"
     22 #include "taler/taler_pq_lib.h"
     23 #include "exchange-database/get_reserve_history.h"
     24 #include "exchange-database/start_read_committed.h"
     25 #include "exchange-database/commit.h"
     26 #include "exchange-database/rollback.h"
     27 #include "helper.h"
     28 
     29 /**
     30  * How often do we re-try when encountering DB serialization issues?
     31  * (We are read-only, so can only happen due to concurrent insert,
     32  * which should be very rare.)
     33  */
     34 #define RETRIES 3
     35 
     36 
     37 /**
     38  * Closure for callbacks invoked via #TALER_EXCHANGEDB_get_reserve_history().
     39  */
     40 struct ReserveHistoryContext
     41 {
     42 
     43   /**
     44    * Which reserve are we building the history for?
     45    */
     46   const struct TALER_ReservePublicKeyP *reserve_pub;
     47 
     48   /**
     49    * Where we build the history.
     50    */
     51   struct TALER_EXCHANGEDB_ReserveHistory *rh;
     52 
     53   /**
     54    * Tail of @e rh list.
     55    */
     56   struct TALER_EXCHANGEDB_ReserveHistory *rh_tail;
     57 
     58   /**
     59    * Plugin context.
     60    */
     61   struct TALER_EXCHANGEDB_PostgresContext *pg;
     62 
     63   /**
     64    * Current reserve_history_serial_id being processed,
     65    * set before each sub-table callback.
     66    */
     67   uint64_t current_history_offset;
     68 
     69   /**
     70    * Set to true on serious internal errors during
     71    * the callbacks.
     72    */
     73   bool failed;
     74 };
     75 
     76 
     77 /**
     78  * Append and return a fresh element to the reserve
     79  * history kept in @a rhc.
     80  *
     81  * @param rhc where the history is kept
     82  * @return the fresh element that was added
     83  */
     84 static struct TALER_EXCHANGEDB_ReserveHistory *
     85 append_rh (struct ReserveHistoryContext *rhc)
     86 {
     87   struct TALER_EXCHANGEDB_ReserveHistory *tail;
     88 
     89   tail = GNUNET_new (struct TALER_EXCHANGEDB_ReserveHistory);
     90   tail->history_offset = rhc->current_history_offset;
     91   if (NULL != rhc->rh_tail)
     92   {
     93     rhc->rh_tail->next = tail;
     94     rhc->rh_tail = tail;
     95   }
     96   else
     97   {
     98     rhc->rh_tail = tail;
     99     rhc->rh = tail;
    100   }
    101   return tail;
    102 }
    103 
    104 
    105 /**
    106  * Add bank transfers to result set for #TALER_EXCHANGEDB_get_reserve_history.
    107  *
    108  * @param cls a `struct ReserveHistoryContext *`
    109  * @param result SQL result
    110  * @param num_results number of rows in @a result
    111  */
    112 static void
    113 add_bank_to_exchange (void *cls,
    114                       PGresult *result,
    115                       unsigned int num_results)
    116 {
    117   struct ReserveHistoryContext *rhc = cls;
    118   struct TALER_EXCHANGEDB_PostgresContext *pg = rhc->pg;
    119 
    120   while (0 < num_results)
    121   {
    122     struct TALER_EXCHANGEDB_BankTransfer *bt;
    123     struct TALER_EXCHANGEDB_ReserveHistory *tail;
    124 
    125     bt = GNUNET_new (struct TALER_EXCHANGEDB_BankTransfer);
    126     {
    127       struct GNUNET_PQ_ResultSpec rs[] = {
    128         GNUNET_PQ_result_spec_uint64 ("wire_reference",
    129                                       &bt->wire_reference),
    130         TALER_PQ_RESULT_SPEC_AMOUNT ("credit",
    131                                      &bt->amount),
    132         GNUNET_PQ_result_spec_timestamp ("execution_date",
    133                                          &bt->execution_date),
    134         GNUNET_PQ_result_spec_string ("sender_account_details",
    135                                       &bt->sender_account_details.full_payto),
    136         GNUNET_PQ_result_spec_end
    137       };
    138 
    139       if (GNUNET_OK !=
    140           GNUNET_PQ_extract_result (result,
    141                                     rs,
    142                                     --num_results))
    143       {
    144         GNUNET_break (0);
    145         GNUNET_free (bt);
    146         rhc->failed = true;
    147         return;
    148       }
    149     }
    150     bt->reserve_pub = *rhc->reserve_pub;
    151     tail = append_rh (rhc);
    152     tail->type = TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE;
    153     tail->details.bank = bt;
    154   } /* end of 'while (0 < rows)' */
    155 }
    156 
    157 
    158 /**
    159  * Add coin withdrawals to result set for #TALER_EXCHANGEDB_get_reserve_history.
    160  *
    161  * @param cls a `struct ReserveHistoryContext *`
    162  * @param result SQL result
    163  * @param num_results number of rows in @a result
    164  */
    165 static void
    166 add_withdraw (void *cls,
    167               PGresult *result,
    168               unsigned int num_results)
    169 {
    170   struct ReserveHistoryContext *rhc = cls;
    171   struct TALER_EXCHANGEDB_PostgresContext *pg = rhc->pg;
    172 
    173   while (0 < num_results)
    174   {
    175     struct TALER_EXCHANGEDB_Withdraw *wd;
    176     struct TALER_EXCHANGEDB_ReserveHistory *tail;
    177 
    178     wd = GNUNET_new (struct TALER_EXCHANGEDB_Withdraw);
    179     {
    180       bool no_noreveal_index;
    181       bool no_max_age;
    182       bool no_selected_h;
    183       size_t num_denom_hs;
    184       size_t num_denom_serials;
    185       uint64_t *my_denom_serials = NULL;
    186       struct TALER_DenominationHashP *my_denom_pub_hashes = NULL;
    187       struct GNUNET_PQ_ResultSpec rs[] = {
    188         GNUNET_PQ_result_spec_auto_from_type  ("planchets_h",
    189                                                &wd->planchets_h),
    190         GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
    191                                               &wd->reserve_sig),
    192         TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
    193                                      &wd->amount_with_fee),
    194         GNUNET_PQ_result_spec_allow_null (
    195           GNUNET_PQ_result_spec_uint16 ("max_age",
    196                                         &wd->max_age),
    197           &no_max_age),
    198         GNUNET_PQ_result_spec_allow_null (
    199           GNUNET_PQ_result_spec_uint16 ("noreveal_index",
    200                                         &wd->noreveal_index),
    201           &no_noreveal_index),
    202         GNUNET_PQ_result_spec_allow_null (
    203           GNUNET_PQ_result_spec_auto_from_type ("blinding_seed",
    204                                                 &wd->blinding_seed),
    205           &wd->no_blinding_seed),
    206         GNUNET_PQ_result_spec_allow_null (
    207           GNUNET_PQ_result_spec_auto_from_type ("selected_h",
    208                                                 &wd->selected_h),
    209           &no_selected_h),
    210         TALER_PQ_result_spec_array_denom_hash (pg->conn,
    211                                                "denom_pub_hashes",
    212                                                &num_denom_hs,
    213                                                &my_denom_pub_hashes),
    214         GNUNET_PQ_result_spec_array_uint64 (pg->conn,
    215                                             "denom_serials",
    216                                             &num_denom_serials,
    217                                             &my_denom_serials),
    218         GNUNET_PQ_result_spec_end
    219       };
    220 
    221       if (GNUNET_OK !=
    222           GNUNET_PQ_extract_result (result,
    223                                     rs,
    224                                     --num_results))
    225       {
    226         GNUNET_break (0);
    227         GNUNET_free (wd);
    228         rhc->failed = true;
    229         GNUNET_PQ_cleanup_result (rs);
    230         return;
    231       }
    232 
    233       if (num_denom_hs != num_denom_serials)
    234       {
    235         GNUNET_break (0);
    236         GNUNET_free (wd);
    237         rhc->failed = true;
    238         GNUNET_PQ_cleanup_result (rs);
    239         return;
    240       }
    241 
    242       if ((no_noreveal_index != no_max_age) ||
    243           (no_noreveal_index != no_selected_h))
    244       {
    245         GNUNET_break (0);
    246         GNUNET_free (wd);
    247         rhc->failed = true;
    248         GNUNET_PQ_cleanup_result (rs);
    249         return;
    250       }
    251       wd->age_proof_required = ! no_max_age;
    252       wd->num_coins = num_denom_serials;
    253       wd->reserve_pub = *rhc->reserve_pub;
    254       wd->denom_serials = my_denom_serials;
    255       wd->denom_pub_hashes = my_denom_pub_hashes;
    256       /* prevent cleanup from destroying our actual result */
    257       my_denom_serials = NULL;
    258       my_denom_pub_hashes = NULL;
    259       GNUNET_PQ_cleanup_result (rs);
    260     }
    261 
    262     tail = append_rh (rhc);
    263     tail->type = TALER_EXCHANGEDB_RO_WITHDRAW_COINS;
    264     tail->details.withdraw = wd;
    265   }
    266 }
    267 
    268 
    269 /**
    270  * Add recoups to result set for #TALER_EXCHANGEDB_get_reserve_history.
    271  *
    272  * @param cls a `struct ReserveHistoryContext *`
    273  * @param result SQL result
    274  * @param num_results number of rows in @a result
    275  */
    276 static void
    277 add_recoup (void *cls,
    278             PGresult *result,
    279             unsigned int num_results)
    280 {
    281   struct ReserveHistoryContext *rhc = cls;
    282   struct TALER_EXCHANGEDB_PostgresContext *pg = rhc->pg;
    283 
    284   while (0 < num_results)
    285   {
    286     struct TALER_EXCHANGEDB_Recoup *recoup;
    287     struct TALER_EXCHANGEDB_ReserveHistory *tail;
    288 
    289     recoup = GNUNET_new (struct TALER_EXCHANGEDB_Recoup);
    290     {
    291       struct GNUNET_PQ_ResultSpec rs[] = {
    292         TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
    293                                      &recoup->value),
    294         GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
    295                                               &recoup->coin.coin_pub),
    296         GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
    297                                               &recoup->coin_blind),
    298         GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
    299                                               &recoup->coin_sig),
    300         GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
    301                                          &recoup->timestamp),
    302         GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
    303                                               &recoup->coin.denom_pub_hash),
    304         TALER_PQ_result_spec_denom_sig (
    305           "denom_sig",
    306           &recoup->coin.denom_sig),
    307         GNUNET_PQ_result_spec_end
    308       };
    309 
    310       if (GNUNET_OK !=
    311           GNUNET_PQ_extract_result (result,
    312                                     rs,
    313                                     --num_results))
    314       {
    315         GNUNET_break (0);
    316         GNUNET_free (recoup);
    317         rhc->failed = true;
    318         return;
    319       }
    320     }
    321     recoup->reserve_pub = *rhc->reserve_pub;
    322     tail = append_rh (rhc);
    323     tail->type = TALER_EXCHANGEDB_RO_RECOUP_COIN;
    324     tail->details.recoup = recoup;
    325   } /* end of 'while (0 < rows)' */
    326 }
    327 
    328 
    329 /**
    330  * Add exchange-to-bank transfers to result set for
    331  * #TALER_EXCHANGEDB_get_reserve_history.
    332  *
    333  * @param cls a `struct ReserveHistoryContext *`
    334  * @param result SQL result
    335  * @param num_results number of rows in @a result
    336  */
    337 static void
    338 add_exchange_to_bank (void *cls,
    339                       PGresult *result,
    340                       unsigned int num_results)
    341 {
    342   struct ReserveHistoryContext *rhc = cls;
    343   struct TALER_EXCHANGEDB_PostgresContext *pg = rhc->pg;
    344 
    345   while (0 < num_results)
    346   {
    347     struct TALER_EXCHANGEDB_ClosingTransfer *closing;
    348     struct TALER_EXCHANGEDB_ReserveHistory *tail;
    349 
    350     closing = GNUNET_new (struct TALER_EXCHANGEDB_ClosingTransfer);
    351     {
    352       struct GNUNET_PQ_ResultSpec rs[] = {
    353         TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
    354                                      &closing->amount),
    355         TALER_PQ_RESULT_SPEC_AMOUNT ("closing_fee",
    356                                      &closing->closing_fee),
    357         GNUNET_PQ_result_spec_timestamp ("execution_date",
    358                                          &closing->execution_date),
    359         GNUNET_PQ_result_spec_string ("receiver_account",
    360                                       &closing->receiver_account_details.
    361                                       full_payto),
    362         GNUNET_PQ_result_spec_auto_from_type ("wtid",
    363                                               &closing->wtid),
    364         GNUNET_PQ_result_spec_end
    365       };
    366 
    367       if (GNUNET_OK !=
    368           GNUNET_PQ_extract_result (result,
    369                                     rs,
    370                                     --num_results))
    371       {
    372         GNUNET_break (0);
    373         GNUNET_free (closing);
    374         rhc->failed = true;
    375         return;
    376       }
    377     }
    378     closing->reserve_pub = *rhc->reserve_pub;
    379     tail = append_rh (rhc);
    380     tail->type = TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK;
    381     tail->details.closing = closing;
    382   } /* end of 'while (0 < rows)' */
    383 }
    384 
    385 
    386 /**
    387  * Add purse merge transfers to result set for
    388  * #TALER_EXCHANGEDB_get_reserve_history.
    389  *
    390  * @param cls a `struct ReserveHistoryContext *`
    391  * @param result SQL result
    392  * @param num_results number of rows in @a result
    393  */
    394 static void
    395 add_p2p_merge (void *cls,
    396                PGresult *result,
    397                unsigned int num_results)
    398 {
    399   struct ReserveHistoryContext *rhc = cls;
    400   struct TALER_EXCHANGEDB_PostgresContext *pg = rhc->pg;
    401 
    402   while (0 < num_results)
    403   {
    404     struct TALER_EXCHANGEDB_PurseMerge *merge;
    405     struct TALER_EXCHANGEDB_ReserveHistory *tail;
    406 
    407     merge = GNUNET_new (struct TALER_EXCHANGEDB_PurseMerge);
    408     {
    409       uint32_t flags32;
    410       struct TALER_Amount balance;
    411       struct GNUNET_PQ_ResultSpec rs[] = {
    412         TALER_PQ_RESULT_SPEC_AMOUNT ("purse_fee",
    413                                      &merge->purse_fee),
    414         TALER_PQ_RESULT_SPEC_AMOUNT ("balance",
    415                                      &balance),
    416         TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
    417                                      &merge->amount_with_fee),
    418         GNUNET_PQ_result_spec_timestamp ("merge_timestamp",
    419                                          &merge->merge_timestamp),
    420         GNUNET_PQ_result_spec_timestamp ("purse_expiration",
    421                                          &merge->purse_expiration),
    422         GNUNET_PQ_result_spec_uint32 ("age_limit",
    423                                       &merge->min_age),
    424         GNUNET_PQ_result_spec_uint32 ("flags",
    425                                       &flags32),
    426         GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
    427                                               &merge->h_contract_terms),
    428         GNUNET_PQ_result_spec_auto_from_type ("merge_pub",
    429                                               &merge->merge_pub),
    430         GNUNET_PQ_result_spec_auto_from_type ("purse_pub",
    431                                               &merge->purse_pub),
    432         GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
    433                                               &merge->reserve_sig),
    434         GNUNET_PQ_result_spec_end
    435       };
    436 
    437       if (GNUNET_OK !=
    438           GNUNET_PQ_extract_result (result,
    439                                     rs,
    440                                     --num_results))
    441       {
    442         GNUNET_break (0);
    443         GNUNET_free (merge);
    444         rhc->failed = true;
    445         return;
    446       }
    447       merge->flags = (enum TALER_WalletAccountMergeFlags) flags32;
    448       if ( (! GNUNET_TIME_absolute_is_future (
    449               merge->merge_timestamp.abs_time)) &&
    450            (-1 != TALER_amount_cmp (&balance,
    451                                     &merge->amount_with_fee)) )
    452         merge->merged = true;
    453     }
    454     merge->reserve_pub = *rhc->reserve_pub;
    455     tail = append_rh (rhc);
    456     tail->type = TALER_EXCHANGEDB_RO_PURSE_MERGE;
    457     tail->details.merge = merge;
    458   }
    459 }
    460 
    461 
    462 /**
    463  * Add paid for history requests to result set for
    464  * #TALER_EXCHANGEDB_get_reserve_history.
    465  *
    466  * @param cls a `struct ReserveHistoryContext *`
    467  * @param result SQL result
    468  * @param num_results number of rows in @a result
    469  */
    470 static void
    471 add_open_requests (void *cls,
    472                    PGresult *result,
    473                    unsigned int num_results)
    474 {
    475   struct ReserveHistoryContext *rhc = cls;
    476   struct TALER_EXCHANGEDB_PostgresContext *pg = rhc->pg;
    477 
    478   while (0 < num_results)
    479   {
    480     struct TALER_EXCHANGEDB_OpenRequest *orq;
    481     struct TALER_EXCHANGEDB_ReserveHistory *tail;
    482 
    483     orq = GNUNET_new (struct TALER_EXCHANGEDB_OpenRequest);
    484     {
    485       struct GNUNET_PQ_ResultSpec rs[] = {
    486         TALER_PQ_RESULT_SPEC_AMOUNT ("open_fee",
    487                                      &orq->open_fee),
    488         GNUNET_PQ_result_spec_timestamp ("request_timestamp",
    489                                          &orq->request_timestamp),
    490         GNUNET_PQ_result_spec_timestamp ("expiration_date",
    491                                          &orq->reserve_expiration),
    492         GNUNET_PQ_result_spec_uint32 ("requested_purse_limit",
    493                                       &orq->purse_limit),
    494         GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
    495                                               &orq->reserve_sig),
    496         GNUNET_PQ_result_spec_end
    497       };
    498 
    499       if (GNUNET_OK !=
    500           GNUNET_PQ_extract_result (result,
    501                                     rs,
    502                                     --num_results))
    503       {
    504         GNUNET_break (0);
    505         GNUNET_free (orq);
    506         rhc->failed = true;
    507         return;
    508       }
    509     }
    510     orq->reserve_pub = *rhc->reserve_pub;
    511     tail = append_rh (rhc);
    512     tail->type = TALER_EXCHANGEDB_RO_OPEN_REQUEST;
    513     tail->details.open_request = orq;
    514   }
    515 }
    516 
    517 
    518 /**
    519  * Add paid for history requests to result set for
    520  * #TALER_EXCHANGEDB_get_reserve_history.
    521  *
    522  * @param cls a `struct ReserveHistoryContext *`
    523  * @param result SQL result
    524  * @param num_results number of rows in @a result
    525  */
    526 static void
    527 add_close_requests (void *cls,
    528                     PGresult *result,
    529                     unsigned int num_results)
    530 {
    531   struct ReserveHistoryContext *rhc = cls;
    532 
    533   while (0 < num_results)
    534   {
    535     struct TALER_EXCHANGEDB_CloseRequest *crq;
    536     struct TALER_EXCHANGEDB_ReserveHistory *tail;
    537 
    538     crq = GNUNET_new (struct TALER_EXCHANGEDB_CloseRequest);
    539     {
    540       struct TALER_FullPayto payto_uri;
    541       struct GNUNET_PQ_ResultSpec rs[] = {
    542         GNUNET_PQ_result_spec_timestamp ("close_timestamp",
    543                                          &crq->request_timestamp),
    544         GNUNET_PQ_result_spec_string ("payto_uri",
    545                                       &payto_uri.full_payto),
    546         GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
    547                                               &crq->reserve_sig),
    548         GNUNET_PQ_result_spec_end
    549       };
    550 
    551       if (GNUNET_OK !=
    552           GNUNET_PQ_extract_result (result,
    553                                     rs,
    554                                     --num_results))
    555       {
    556         GNUNET_break (0);
    557         GNUNET_free (crq);
    558         rhc->failed = true;
    559         return;
    560       }
    561       TALER_full_payto_hash (payto_uri,
    562                              &crq->target_account_h_payto);
    563       GNUNET_free (payto_uri.full_payto);
    564     }
    565     crq->reserve_pub = *rhc->reserve_pub;
    566     tail = append_rh (rhc);
    567     tail->type = TALER_EXCHANGEDB_RO_CLOSE_REQUEST;
    568     tail->details.close_request = crq;
    569   }
    570 }
    571 
    572 
    573 /**
    574  * Add reserve history entries found.
    575  *
    576  * @param cls a `struct ReserveHistoryContext *`
    577  * @param result SQL result
    578  * @param num_results number of rows in @a result
    579  */
    580 static void
    581 handle_history_entry (void *cls,
    582                       PGresult *result,
    583                       unsigned int num_results)
    584 {
    585   static const struct
    586   {
    587     /**
    588      * Table with reserve history entry we are responsible for.
    589      */
    590     const char *table;
    591     /**
    592      * Name of the prepared statement to run.
    593      */
    594     const char *statement;
    595     /**
    596      * Function to use to process the results.
    597      */
    598     GNUNET_PQ_PostgresResultHandler cb;
    599   } work[] = {
    600     /** #TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE */
    601     { "reserves_in",
    602       "reserves_in_get_transactions",
    603       add_bank_to_exchange },
    604     /** #TALER_EXCHANGEDB_RO_WITHDRAW_COINS */
    605     { "withdraw",
    606       "get_withdraw_details",
    607       &add_withdraw },
    608     /** #TALER_EXCHANGEDB_RO_RECOUP_COIN */
    609     { "recoup",
    610       "recoup_by_reserve",
    611       &add_recoup },
    612     /** #TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK */
    613     { "reserves_close",
    614       "close_by_reserve",
    615       &add_exchange_to_bank },
    616     /** #TALER_EXCHANGEDB_RO_PURSE_MERGE */
    617     { "purse_decision",
    618       "merge_by_reserve",
    619       &add_p2p_merge },
    620     /** #TALER_EXCHANGEDB_RO_OPEN_REQUEST */
    621     { "reserves_open_requests",
    622       "open_request_by_reserve",
    623       &add_open_requests },
    624     /** #TALER_EXCHANGEDB_RO_CLOSE_REQUEST */
    625     { "close_requests",
    626       "close_request_by_reserve",
    627       &add_close_requests },
    628     /* List terminator */
    629     { NULL, NULL, NULL }
    630   };
    631   struct ReserveHistoryContext *rhc = cls;
    632   char *table_name;
    633   uint64_t serial_id;
    634   struct GNUNET_PQ_ResultSpec rs[] = {
    635     GNUNET_PQ_result_spec_string ("table_name",
    636                                   &table_name),
    637     GNUNET_PQ_result_spec_uint64 ("serial_id",
    638                                   &serial_id),
    639     GNUNET_PQ_result_spec_uint64 ("reserve_history_serial_id",
    640                                   &rhc->current_history_offset),
    641     GNUNET_PQ_result_spec_end
    642   };
    643   struct GNUNET_PQ_QueryParam params[] = {
    644     GNUNET_PQ_query_param_auto_from_type (rhc->reserve_pub),
    645     GNUNET_PQ_query_param_uint64 (&serial_id),
    646     GNUNET_PQ_query_param_end
    647   };
    648 
    649   while (0 < num_results--)
    650   {
    651     enum GNUNET_DB_QueryStatus qs;
    652     bool found = false;
    653 
    654     if (GNUNET_OK !=
    655         GNUNET_PQ_extract_result (result,
    656                                   rs,
    657                                   num_results))
    658     {
    659       GNUNET_break (0);
    660       rhc->failed = true;
    661       return;
    662     }
    663 
    664     for (unsigned int i = 0;
    665          NULL != work[i].cb;
    666          i++)
    667     {
    668       if (0 != strcmp (table_name,
    669                        work[i].table))
    670         continue;
    671       found = true;
    672       qs = GNUNET_PQ_eval_prepared_multi_select (rhc->pg->conn,
    673                                                  work[i].statement,
    674                                                  params,
    675                                                  work[i].cb,
    676                                                  rhc);
    677       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    678                   "Reserve %s had %d transactions at %llu in table %s\n",
    679                   TALER_B2S (rhc->reserve_pub),
    680                   (int) qs,
    681                   (unsigned long long) serial_id,
    682                   table_name);
    683       if (0 >= qs)
    684         rhc->failed = true;
    685       break;
    686     }
    687     if (! found)
    688     {
    689       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    690                   "Reserve history includes unsupported table `%s`\n",
    691                   table_name);
    692       rhc->failed = true;
    693     }
    694     GNUNET_PQ_cleanup_result (rs);
    695     if (rhc->failed)
    696       break;
    697   }
    698 }
    699 
    700 
    701 enum GNUNET_DB_QueryStatus
    702 TALER_EXCHANGEDB_get_reserve_history (
    703   struct TALER_EXCHANGEDB_PostgresContext *pg,
    704   const struct TALER_ReservePublicKeyP *reserve_pub,
    705   uint64_t start_off,
    706   uint64_t etag_in,
    707   uint64_t *etag_out,
    708   struct TALER_Amount *balance,
    709   struct TALER_EXCHANGEDB_ReserveHistory **rhp)
    710 {
    711   struct ReserveHistoryContext rhc = {
    712     .pg = pg,
    713     .reserve_pub = reserve_pub
    714   };
    715   struct GNUNET_PQ_QueryParam params[] = {
    716     GNUNET_PQ_query_param_auto_from_type (reserve_pub),
    717     GNUNET_PQ_query_param_end
    718   };
    719   struct GNUNET_PQ_QueryParam lparams[] = {
    720     GNUNET_PQ_query_param_auto_from_type (reserve_pub),
    721     GNUNET_PQ_query_param_uint64 (&start_off),
    722     GNUNET_PQ_query_param_end
    723   };
    724 
    725   *rhp = NULL;
    726   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    727               "Getting transactions for reserve %s\n",
    728               TALER_B2S (reserve_pub));
    729   PREPARE (pg,
    730            "get_reserve_history_etag",
    731            "SELECT"
    732            " his.reserve_history_serial_id"
    733            ",r.current_balance"
    734            " FROM reserve_history his"
    735            " JOIN reserves r USING (reserve_pub)"
    736            " WHERE his.reserve_pub=$1"
    737            " ORDER BY reserve_history_serial_id DESC"
    738            " LIMIT 1;");
    739   PREPARE (pg,
    740            "get_reserve_history",
    741            "SELECT"
    742            " table_name"
    743            ",serial_id"
    744            ",reserve_history_serial_id"
    745            " FROM reserve_history"
    746            " WHERE reserve_pub=$1"
    747            "   AND reserve_history_serial_id > $2"
    748            " ORDER BY reserve_history_serial_id DESC;");
    749   PREPARE (pg,
    750            "reserves_in_get_transactions",
    751            "SELECT"
    752            " ri.wire_reference"
    753            ",ri.credit"
    754            ",ri.execution_date"
    755            ",wt.payto_uri AS sender_account_details"
    756            " FROM reserves_in ri"
    757            " JOIN wire_targets wt"
    758            "   ON (wire_source_h_payto = wire_target_h_payto)"
    759            " WHERE ri.reserve_pub=$1"
    760            "   AND ri.reserve_in_serial_id=$2;");
    761   PREPARE (pg,
    762            "get_withdraw_details",
    763            "SELECT"
    764            " planchets_h"
    765            ",amount_with_fee"
    766            ",reserve_sig"
    767            ",max_age"
    768            ",noreveal_index"
    769            ",selected_h"
    770            ",blinding_seed"
    771            ",denom_serials"
    772            ",ARRAY("
    773            "  SELECT denominations.denom_pub_hash FROM ("
    774            "    SELECT UNNEST(denom_serials) AS id,"
    775            "           generate_subscripts(denom_serials, 1) AS nr" /* for order */
    776            "  ) AS denoms"
    777            "  LEFT JOIN denominations ON denominations.denominations_serial=denoms.id"
    778            ") AS denom_pub_hashes"
    779            " FROM withdraw "
    780            " WHERE withdraw_id=$2"
    781            " AND reserve_pub=$1;");
    782   PREPARE (pg,
    783            "recoup_by_reserve",
    784            "SELECT"
    785            " rec.coin_pub"
    786            ",rec.coin_sig"
    787            ",rec.coin_blind"
    788            ",rec.amount"
    789            ",rec.recoup_timestamp"
    790            ",denom.denom_pub_hash"
    791            ",kc.denom_sig"
    792            " FROM recoup rec"
    793            " JOIN withdraw ro"
    794            "   USING (withdraw_id)"
    795            " JOIN reserves res"
    796            "   USING (reserve_pub)"
    797            " JOIN known_coins kc"
    798            "   USING (coin_pub)"
    799            " JOIN denominations denom"
    800            "   ON (denom.denominations_serial = kc.denominations_serial)"
    801            " WHERE rec.recoup_uuid=$2"
    802            "   AND res.reserve_pub=$1;");
    803   PREPARE (pg,
    804            "close_by_reserve",
    805            "SELECT"
    806            " rc.amount"
    807            ",rc.closing_fee"
    808            ",rc.execution_date"
    809            ",wt.payto_uri AS receiver_account"
    810            ",rc.wtid"
    811            " FROM reserves_close rc"
    812            " JOIN wire_targets wt"
    813            "   USING (wire_target_h_payto)"
    814            " WHERE reserve_pub=$1"
    815            "   AND close_uuid=$2;");
    816   PREPARE (pg,
    817            "merge_by_reserve",
    818            "SELECT"
    819            " pr.amount_with_fee"
    820            ",pr.balance"
    821            ",pr.purse_fee"
    822            ",pr.h_contract_terms"
    823            ",pr.merge_pub"
    824            ",am.reserve_sig"
    825            ",pm.purse_pub"
    826            ",pm.merge_timestamp"
    827            ",pr.purse_expiration"
    828            ",pr.age_limit"
    829            ",pr.flags"
    830            " FROM purse_decision pdes"
    831            "   JOIN purse_requests pr"
    832            "     ON (pr.purse_pub = pdes.purse_pub)"
    833            "   JOIN purse_merges pm"
    834            "     ON (pm.purse_pub = pdes.purse_pub)"
    835            "   JOIN account_merges am"
    836            "     ON (am.purse_pub = pm.purse_pub AND"
    837            "         am.reserve_pub = pm.reserve_pub)"
    838            " WHERE pdes.purse_decision_serial_id=$2"
    839            "  AND pm.reserve_pub=$1"
    840            "  AND COALESCE(pm.partner_serial_id,0)=0" /* must be local! */
    841            "  AND NOT pdes.refunded;");
    842   PREPARE (pg,
    843            "open_request_by_reserve",
    844            "SELECT"
    845            " reserve_payment AS open_fee"
    846            ",request_timestamp"
    847            ",expiration_date"
    848            ",requested_purse_limit"
    849            ",reserve_sig"
    850            " FROM reserves_open_requests"
    851            " WHERE reserve_pub=$1"
    852            "   AND open_request_uuid=$2;");
    853   PREPARE (pg,
    854            "close_request_by_reserve",
    855            "SELECT"
    856            " close_timestamp"
    857            ",payto_uri"
    858            ",reserve_sig"
    859            " FROM close_requests"
    860            " WHERE reserve_pub=$1"
    861            "   AND close_request_serial_id=$2;");
    862 
    863   for (unsigned int i = 0; i<RETRIES; i++)
    864   {
    865     enum GNUNET_DB_QueryStatus qs;
    866     uint64_t end;
    867     struct GNUNET_PQ_ResultSpec rs[] = {
    868       GNUNET_PQ_result_spec_uint64 ("reserve_history_serial_id",
    869                                     &end),
    870       TALER_PQ_RESULT_SPEC_AMOUNT ("current_balance",
    871                                    balance),
    872       GNUNET_PQ_result_spec_end
    873     };
    874 
    875     if (GNUNET_OK !=
    876         TALER_TALER_EXCHANGEDB_start_read_committed (pg,
    877                                                      "get-reserve-transactions")
    878         )
    879     {
    880       GNUNET_break (0);
    881       return GNUNET_DB_STATUS_HARD_ERROR;
    882     }
    883     /* First only check the last item, to see if
    884        we even need to iterate */
    885     qs = GNUNET_PQ_eval_prepared_singleton_select (
    886       pg->conn,
    887       "get_reserve_history_etag",
    888       params,
    889       rs);
    890     switch (qs)
    891     {
    892     case GNUNET_DB_STATUS_HARD_ERROR:
    893       TALER_EXCHANGEDB_rollback (pg);
    894       return qs;
    895     case GNUNET_DB_STATUS_SOFT_ERROR:
    896       TALER_EXCHANGEDB_rollback (pg);
    897       continue;
    898     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    899       TALER_EXCHANGEDB_rollback (pg);
    900       return qs;
    901     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    902       *etag_out = end;
    903       if (end == etag_in)
    904         return qs;
    905     }
    906     /* We indeed need to iterate over the history */
    907     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    908                 "Current ETag for reserve %s is %llu\n",
    909                 TALER_B2S (reserve_pub),
    910                 (unsigned long long) end);
    911 
    912     qs = GNUNET_PQ_eval_prepared_multi_select (
    913       pg->conn,
    914       "get_reserve_history",
    915       lparams,
    916       &handle_history_entry,
    917       &rhc);
    918     switch (qs)
    919     {
    920     case GNUNET_DB_STATUS_HARD_ERROR:
    921       TALER_EXCHANGEDB_rollback (pg);
    922       return qs;
    923     case GNUNET_DB_STATUS_SOFT_ERROR:
    924       TALER_EXCHANGEDB_rollback (pg);
    925       TALER_EXCHANGEDB_free_reserve_history (rhc.rh);
    926       rhc.rh = NULL;
    927       rhc.rh_tail = NULL;
    928       continue;
    929     default:
    930       break;
    931     }
    932     if (rhc.failed)
    933     {
    934       TALER_EXCHANGEDB_rollback (pg);
    935       TALER_EXCHANGEDB_free_reserve_history (rhc.rh);
    936       return GNUNET_DB_STATUS_SOFT_ERROR;
    937     }
    938     qs = TALER_EXCHANGEDB_commit (pg);
    939     switch (qs)
    940     {
    941     case GNUNET_DB_STATUS_HARD_ERROR:
    942       TALER_EXCHANGEDB_free_reserve_history (rhc.rh);
    943       return qs;
    944     case GNUNET_DB_STATUS_SOFT_ERROR:
    945       TALER_EXCHANGEDB_free_reserve_history (rhc.rh);
    946       rhc.rh = NULL;
    947       rhc.rh_tail = NULL;
    948       continue;
    949     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    950     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    951       *rhp = rhc.rh;
    952       return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
    953     }
    954   }
    955   return GNUNET_DB_STATUS_SOFT_ERROR;
    956 }