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


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