exchange

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

pg_get_reserve_history.c (31203B)


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