exchange

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

taler-exchange-httpd_post-recoup-refresh.c (29136B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2017-2026 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Affero 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 Affero General Public License for more details.
     12 
     13   You should have received a copy of the GNU Affero General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file taler-exchange-httpd_post-recoup-refresh.c
     18  * @brief Handle /recoup-refresh requests: coins of revoked denominations
     19  *        that originated from one refresh operation are paid back to the
     20  *        old coin that was melted.  Structured like the /withdraw handler
     21  *        as a phase state machine.
     22  * @author Christian Grothoff
     23  * @author Özgür Kesim
     24  */
     25 #include <gnunet/gnunet_util_lib.h>
     26 #include <gnunet/gnunet_json_lib.h>
     27 #include <jansson.h>
     28 #include <microhttpd.h>
     29 #include "taler/taler_json_lib.h"
     30 #include "taler/taler_mhd_lib.h"
     31 #include "taler-exchange-httpd.h"
     32 #include "taler-exchange-httpd_db.h"
     33 #include "taler-exchange-httpd_common_recoup.h"
     34 #include "taler-exchange-httpd_post-recoup-refresh.h"
     35 #include "taler-exchange-httpd_responses.h"
     36 #include "taler-exchange-httpd_get-keys.h"
     37 #include "taler-exchange-httpd_get-metrics.h"
     38 #include "exchangedb_lib.h"
     39 #include "exchange-database/do_recoup_refresh.h"
     40 #include "exchange-database/get_refresh.h"
     41 #include "exchange-database/rollback.h"
     42 
     43 
     44 /**
     45  * The different types of errors that might occur, sorted by name.
     46  */
     47 enum RecoupRefreshError
     48 {
     49   RECOUP_REFRESH_ERROR_NONE,
     50   RECOUP_REFRESH_ERROR_BATCH_SIZE_MISMATCH,
     51   RECOUP_REFRESH_ERROR_BLINDING_FAILED,
     52   RECOUP_REFRESH_ERROR_COIN_SIGNATURE_INVALID,
     53   RECOUP_REFRESH_ERROR_COMMITMENT_MISMATCH,
     54   RECOUP_REFRESH_ERROR_CONFIRMATION_SIGN,
     55   RECOUP_REFRESH_ERROR_DB_FETCH_FAILED,
     56   RECOUP_REFRESH_ERROR_DB_INVARIANT_FAILURE,
     57   RECOUP_REFRESH_ERROR_DENOMINATION_EXPIRED,
     58   RECOUP_REFRESH_ERROR_DENOMINATION_KEY_UNKNOWN,
     59   RECOUP_REFRESH_ERROR_DENOMINATION_MISMATCH,
     60   RECOUP_REFRESH_ERROR_DENOMINATION_NOT_ELIGIBLE,
     61   RECOUP_REFRESH_ERROR_DENOMINATION_SIGNATURE_INVALID,
     62   RECOUP_REFRESH_ERROR_DENOMINATION_VALIDITY_IN_FUTURE,
     63   RECOUP_REFRESH_ERROR_INSUFFICIENT_FUNDS,
     64   RECOUP_REFRESH_ERROR_KEYS_MISSING,
     65   RECOUP_REFRESH_ERROR_REQUEST_PARAMETER_MALFORMED,
     66   RECOUP_REFRESH_ERROR_MELT_NOT_FOUND,
     67 };
     68 
     69 
     70 /**
     71  * Context for a /recoup-refresh request.
     72  */
     73 struct RecoupRefreshContext
     74 {
     75 
     76   /**
     77    * Processing phase we are in.  The ordering matters, as we
     78    * progress through them by incrementing the phase in the happy path.
     79    */
     80   enum
     81   {
     82     RECOUP_REFRESH_PHASE_PARSE = 0,
     83     RECOUP_REFRESH_PHASE_LOOKUP_OPERATION,
     84     RECOUP_REFRESH_PHASE_CHECK_KEYS,
     85     RECOUP_REFRESH_PHASE_VERIFY_COINS,
     86     RECOUP_REFRESH_PHASE_RUN_TRANSACTION,
     87     RECOUP_REFRESH_PHASE_GENERATE_REPLY_SUCCESS,
     88     RECOUP_REFRESH_PHASE_GENERATE_REPLY_ERROR,
     89     RECOUP_REFRESH_PHASE_RETURN_NO,
     90     RECOUP_REFRESH_PHASE_RETURN_YES,
     91   } phase;
     92 
     93   /**
     94    * Request context.
     95    */
     96   const struct TEH_RequestContext *rc;
     97 
     98   /**
     99    * Current time for the DB transaction.
    100    */
    101   struct GNUNET_TIME_Timestamp now;
    102 
    103   /**
    104    * Captures all parameters provided in the JSON request.
    105    */
    106   struct
    107   {
    108     /**
    109      * Old coin the coins were refreshed from and that is credited.
    110      */
    111     struct TALER_CoinSpendPublicKeyP old_coin_pub;
    112 
    113     /**
    114      * Commitment of the refresh operation.
    115      */
    116     struct TALER_RefreshCommitmentP rc;
    117 
    118     /**
    119      * Number of entries in @e coins.
    120      */
    121     size_t num_coins;
    122 
    123     /**
    124      * The coins of the signed batch, in order.
    125      */
    126     struct TEH_RecoupCoin *coins;
    127   } request;
    128 
    129   /**
    130    * The refresh operation, as recorded in the database.
    131    */
    132   struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS refresh;
    133 
    134   /**
    135    * True once @e refresh holds data that must be released.
    136    */
    137   bool have_refresh;
    138 
    139   /**
    140    * Errors occurring during evaluation of the request.  In phase
    141    * #RECOUP_REFRESH_PHASE_GENERATE_REPLY_ERROR an appropriate error
    142    * message is prepared and sent to the client.
    143    */
    144   struct
    145   {
    146     /**
    147      * The (internal) error code.
    148      */
    149     enum RecoupRefreshError code;
    150 
    151     /**
    152      * Details for some of the errors.
    153      */
    154     union
    155     {
    156       const char *request_parameter_malformed;
    157       const char *db_fetch_context;
    158       /**
    159        * For all errors related to a particular denomination.
    160        */
    161       const struct TALER_DenominationHashP *denom_h;
    162       enum TALER_ErrorCode ec_confirmation_sign;
    163       /**
    164        * The coin that has no residual value left.
    165        */
    166       const struct TEH_RecoupCoin *insufficient_funds;
    167     } details;
    168   } error;
    169 };
    170 
    171 
    172 /**
    173  * The following macros set the given error code,
    174  * set the phase to RECOUP_REFRESH_PHASE_GENERATE_REPLY_ERROR,
    175  * and optionally set the given field to the given value.
    176  */
    177 #define SET_ERROR(wc, ec) \
    178         do \
    179         { GNUNET_static_assert (RECOUP_REFRESH_ERROR_NONE != ec); \
    180           (wc)->error.code = (ec);                                 \
    181           (wc)->phase = RECOUP_REFRESH_PHASE_GENERATE_REPLY_ERROR; \
    182         } while (0)
    183 #define SET_ERROR_WITH_DETAIL(wc, ec, field, value) \
    184         do \
    185         { GNUNET_static_assert (RECOUP_REFRESH_ERROR_NONE != ec); \
    186           (wc)->error.code = (ec);                                 \
    187           (wc)->error.details.field = (value);                     \
    188           (wc)->phase = RECOUP_REFRESH_PHASE_GENERATE_REPLY_ERROR; \
    189         } while (0)
    190 
    191 
    192 /**
    193  * Terminate the phase loop with the given MHD result.
    194  *
    195  * @param[in,out] wc context to finish
    196  * @param mres result to return from the handler
    197  */
    198 static void
    199 finish_loop (struct RecoupRefreshContext *wc,
    200              enum MHD_Result mres)
    201 {
    202   wc->phase = (MHD_YES == mres)
    203     ? RECOUP_REFRESH_PHASE_RETURN_YES
    204     : RECOUP_REFRESH_PHASE_RETURN_NO;
    205 }
    206 
    207 
    208 /**
    209  * Translate an error of the shared recoup logic into our error state.
    210  *
    211  * @param[in,out] wc context to set the error on
    212  * @param err error reported by the shared logic
    213  * @param details details reported by the shared logic
    214  */
    215 static void
    216 set_common_error (struct RecoupRefreshContext *wc,
    217                   enum TEH_RecoupError err,
    218                   const union TEH_RecoupErrorDetails *details)
    219 {
    220   switch (err)
    221   {
    222   case TEH_RECOUP_ERROR_NONE:
    223     GNUNET_assert (0);
    224     return;
    225   case TEH_RECOUP_ERROR_REQUEST_PARAMETER_MALFORMED:
    226     SET_ERROR_WITH_DETAIL (wc,
    227                            RECOUP_REFRESH_ERROR_REQUEST_PARAMETER_MALFORMED,
    228                            request_parameter_malformed,
    229                            details->hint);
    230     return;
    231   case TEH_RECOUP_ERROR_KEYS_MISSING:
    232     SET_ERROR (wc,
    233                RECOUP_REFRESH_ERROR_KEYS_MISSING);
    234     return;
    235   case TEH_RECOUP_ERROR_DENOMINATION_KEY_UNKNOWN:
    236     SET_ERROR_WITH_DETAIL (wc,
    237                            RECOUP_REFRESH_ERROR_DENOMINATION_KEY_UNKNOWN,
    238                            denom_h,
    239                            details->denom_h);
    240     return;
    241   case TEH_RECOUP_ERROR_DENOMINATION_MISMATCH:
    242     SET_ERROR_WITH_DETAIL (wc,
    243                            RECOUP_REFRESH_ERROR_DENOMINATION_MISMATCH,
    244                            denom_h,
    245                            details->denom_h);
    246     return;
    247   case TEH_RECOUP_ERROR_DENOMINATION_NOT_ELIGIBLE:
    248     SET_ERROR_WITH_DETAIL (wc,
    249                            RECOUP_REFRESH_ERROR_DENOMINATION_NOT_ELIGIBLE,
    250                            denom_h,
    251                            details->denom_h);
    252     return;
    253   case TEH_RECOUP_ERROR_DENOMINATION_EXPIRED:
    254     SET_ERROR_WITH_DETAIL (wc,
    255                            RECOUP_REFRESH_ERROR_DENOMINATION_EXPIRED,
    256                            denom_h,
    257                            details->denom_h);
    258     return;
    259   case TEH_RECOUP_ERROR_DENOMINATION_VALIDITY_IN_FUTURE:
    260     SET_ERROR_WITH_DETAIL (wc,
    261                            RECOUP_REFRESH_ERROR_DENOMINATION_VALIDITY_IN_FUTURE,
    262                            denom_h,
    263                            details->denom_h);
    264     return;
    265   case TEH_RECOUP_ERROR_DB_INVARIANT_FAILURE:
    266     SET_ERROR (wc,
    267                RECOUP_REFRESH_ERROR_DB_INVARIANT_FAILURE);
    268     return;
    269   case TEH_RECOUP_ERROR_DENOMINATION_SIGNATURE_INVALID:
    270     SET_ERROR (wc,
    271                RECOUP_REFRESH_ERROR_DENOMINATION_SIGNATURE_INVALID);
    272     return;
    273   case TEH_RECOUP_ERROR_COIN_SIGNATURE_INVALID:
    274     SET_ERROR (wc,
    275                RECOUP_REFRESH_ERROR_COIN_SIGNATURE_INVALID);
    276     return;
    277   case TEH_RECOUP_ERROR_BLINDING_FAILED:
    278     SET_ERROR (wc,
    279                RECOUP_REFRESH_ERROR_BLINDING_FAILED);
    280     return;
    281   case TEH_RECOUP_ERROR_COMMITMENT_MISMATCH:
    282     SET_ERROR (wc,
    283                RECOUP_REFRESH_ERROR_COMMITMENT_MISMATCH);
    284     return;
    285   }
    286   GNUNET_assert (0);
    287 }
    288 
    289 
    290 /**
    291  * Parse the request.
    292  *
    293  * @param[in,out] wc context of the request
    294  * @param root the JSON body
    295  */
    296 static void
    297 phase_parse (struct RecoupRefreshContext *wc,
    298              const json_t *root)
    299 {
    300   const json_t *j_coin_data;
    301   struct GNUNET_JSON_Specification spec[] = {
    302     GNUNET_JSON_spec_fixed_auto ("old_coin_pub",
    303                                  &wc->request.old_coin_pub),
    304     GNUNET_JSON_spec_fixed_auto ("rc",
    305                                  &wc->request.rc),
    306     GNUNET_JSON_spec_array_const ("coin_data",
    307                                   &j_coin_data),
    308     GNUNET_JSON_spec_end ()
    309   };
    310   enum GNUNET_GenericReturnValue res;
    311   const char *hint;
    312   enum MHD_Result mret;
    313 
    314   res = TALER_MHD_parse_json_data (wc->rc->connection,
    315                                    root,
    316                                    spec);
    317   if (GNUNET_OK != res)
    318   {
    319     GNUNET_break_op (0);
    320     finish_loop (wc,
    321                  (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES);
    322     return;
    323   }
    324   res = TEH_recoup_parse_coin_data (wc->rc->connection,
    325                                     j_coin_data,
    326                                     &wc->request.num_coins,
    327                                     &wc->request.coins,
    328                                     &hint,
    329                                     &mret);
    330   switch (res)
    331   {
    332   case GNUNET_OK:
    333     wc->phase++;
    334     return;
    335   case GNUNET_NO:
    336     finish_loop (wc,
    337                  mret);
    338     return;
    339   case GNUNET_SYSERR:
    340     SET_ERROR_WITH_DETAIL (wc,
    341                            RECOUP_REFRESH_ERROR_REQUEST_PARAMETER_MALFORMED,
    342                            request_parameter_malformed,
    343                            hint);
    344     return;
    345   }
    346   GNUNET_assert (0);
    347 }
    348 
    349 
    350 /**
    351  * Find the refresh operation the coins originated from.
    352  *
    353  * @param[in,out] wc context of the request
    354  */
    355 static void
    356 phase_lookup_operation (struct RecoupRefreshContext *wc)
    357 {
    358   enum GNUNET_DB_QueryStatus qs;
    359   uint8_t max_retries = 3;
    360 
    361   while (0 < max_retries--)
    362   {
    363     qs = TALER_EXCHANGEDB_get_refresh (TEH_pg,
    364                                        &wc->request.rc,
    365                                        &wc->refresh);
    366     if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
    367       break;
    368   }
    369   if (0 > qs)
    370   {
    371     GNUNET_break (0);
    372     SET_ERROR_WITH_DETAIL (wc,
    373                            RECOUP_REFRESH_ERROR_DB_FETCH_FAILED,
    374                            db_fetch_context,
    375                            "get_refresh");
    376     return;
    377   }
    378   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    379   {
    380     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    381                 "Recoup requested for unknown refresh commitment %s\n",
    382                 GNUNET_h2s (&wc->request.rc.session_hash));
    383     SET_ERROR (wc,
    384                RECOUP_REFRESH_ERROR_MELT_NOT_FOUND);
    385     return;
    386   }
    387   wc->have_refresh = true;
    388   if (0 !=
    389       GNUNET_memcmp (&wc->refresh.coin.coin_pub,
    390                      &wc->request.old_coin_pub))
    391   {
    392     GNUNET_break_op (0);
    393     SET_ERROR (wc,
    394                RECOUP_REFRESH_ERROR_MELT_NOT_FOUND);
    395     return;
    396   }
    397   if (wc->refresh.num_coins != wc->request.num_coins)
    398   {
    399     GNUNET_break_op (0);
    400     SET_ERROR (wc,
    401                RECOUP_REFRESH_ERROR_BATCH_SIZE_MISMATCH);
    402     return;
    403   }
    404   wc->phase++;
    405 }
    406 
    407 
    408 /**
    409  * Check the denominations of the batch.
    410  *
    411  * @param[in,out] wc context of the request
    412  */
    413 static void
    414 phase_check_keys (struct RecoupRefreshContext *wc)
    415 {
    416   union TEH_RecoupErrorDetails details;
    417   enum TEH_RecoupError err;
    418 
    419   err = TEH_recoup_check_keys (wc->request.num_coins,
    420                                wc->request.coins,
    421                                wc->refresh.denom_serials,
    422                                ! wc->refresh.no_blinding_seed,
    423                                &details);
    424   if (TEH_RECOUP_ERROR_NONE != err)
    425   {
    426     set_common_error (wc,
    427                       err,
    428                       &details);
    429     return;
    430   }
    431   wc->phase++;
    432 }
    433 
    434 
    435 /**
    436  * Verify the disclosed coins and that the batch matches the
    437  * commitment of the refresh operation.
    438  *
    439  * @param[in,out] wc context of the request
    440  */
    441 static void
    442 phase_verify_coins (struct RecoupRefreshContext *wc)
    443 {
    444   union TEH_RecoupErrorDetails details;
    445   enum TEH_RecoupError err;
    446 
    447   /* The exchange signed the batch at the noreveal_index, whose
    448      hash was recorded as selected_h. */
    449   err = TEH_recoup_verify_coins (wc->request.num_coins,
    450                                  wc->request.coins,
    451                                  true, /* for melt */
    452                                  wc->refresh.no_blinding_seed
    453                                  ? NULL
    454                                  : &wc->refresh.blinding_seed,
    455                                  wc->refresh.num_cs_r_values,
    456                                  wc->refresh.cs_r_values,
    457                                  &wc->refresh.selected_h,
    458                                  &details);
    459   if (TEH_RECOUP_ERROR_NONE != err)
    460   {
    461     set_common_error (wc,
    462                       err,
    463                       &details);
    464     return;
    465   }
    466   wc->phase++;
    467 }
    468 
    469 
    470 /**
    471  * Function implementing the recoup transaction: credits the old coin for
    472  * every disclosed coin.  IF it returns a non-error code, the transaction
    473  * logic MUST NOT queue a MHD response.  IF it returns a hard error, it
    474  * sets the error state (and the reply is generated by the error phase).
    475  * IF it returns the soft error code, the function MAY be called again to
    476  * retry and MUST not queue a MHD response.
    477  *
    478  * @param cls a `struct RecoupRefreshContext *`
    479  * @param connection MHD request which triggered the transaction
    480  * @param[out] mhd_ret set to MHD response status for @a connection,
    481  *             if transaction failed (!)
    482  * @return transaction status
    483  */
    484 static enum GNUNET_DB_QueryStatus
    485 recoup_transaction (void *cls,
    486                     struct MHD_Connection *connection,
    487                     enum MHD_Result *mhd_ret)
    488 {
    489   struct RecoupRefreshContext *wc = cls;
    490 
    491   /* First, make sure all disclosed coins are known.  This runs inside
    492      of the transaction: on a conflict, the reply is queued and the
    493      rollback removes every coin of this batch again. */
    494   {
    495     const struct TALER_CoinPublicInfo *coins[wc->request.num_coins];
    496     uint64_t known_coin_ids[wc->request.num_coins];
    497     unsigned int num_disclosed = 0;
    498     enum GNUNET_DB_QueryStatus qs;
    499 
    500     for (size_t i = 0; i < wc->request.num_coins; i++)
    501     {
    502       const struct TEH_RecoupCoin *c = &wc->request.coins[i];
    503 
    504       if (c->disclosed)
    505         coins[num_disclosed++] = &c->coin;
    506     }
    507     GNUNET_assert (0 < num_disclosed);
    508     qs = TEH_make_coins_known (num_disclosed,
    509                                coins,
    510                                connection,
    511                                known_coin_ids,
    512                                mhd_ret);
    513     if (qs < 0)
    514       return qs;
    515     num_disclosed = 0;
    516     for (size_t i = 0; i < wc->request.num_coins; i++)
    517     {
    518       struct TEH_RecoupCoin *c = &wc->request.coins[i];
    519 
    520       if (c->disclosed)
    521         c->known_coin_id = known_coin_ids[num_disclosed++];
    522     }
    523   }
    524   for (size_t i = 0; i < wc->request.num_coins; i++)
    525   {
    526     struct TEH_RecoupCoin *c = &wc->request.coins[i];
    527     enum GNUNET_DB_QueryStatus qs;
    528     bool recoup_ok;
    529     bool internal_failure;
    530 
    531     if (! c->disclosed)
    532       continue;
    533     c->timestamp = wc->now;
    534     qs = TALER_EXCHANGEDB_do_recoup_refresh (TEH_pg,
    535                                              &wc->request.old_coin_pub,
    536                                              wc->refresh.refresh_id,
    537                                              (uint32_t) i,
    538                                              &c->coin_blinding_secret,
    539                                              &c->coin.coin_pub,
    540                                              c->known_coin_id,
    541                                              &c->coin_sig,
    542                                              &c->timestamp,
    543                                              &c->amount,
    544                                              &recoup_ok,
    545                                              &internal_failure);
    546     if (0 > qs)
    547     {
    548       if (GNUNET_DB_STATUS_HARD_ERROR == qs)
    549         SET_ERROR_WITH_DETAIL (wc,
    550                                RECOUP_REFRESH_ERROR_DB_FETCH_FAILED,
    551                                db_fetch_context,
    552                                "do_recoup_refresh");
    553       return qs;
    554     }
    555     if (internal_failure)
    556     {
    557       GNUNET_break (0);
    558       SET_ERROR (wc,
    559                  RECOUP_REFRESH_ERROR_DB_INVARIANT_FAILURE);
    560       return GNUNET_DB_STATUS_HARD_ERROR;
    561     }
    562     if (! recoup_ok)
    563     {
    564       /* The reply looks at the coin's history, so end our transaction. */
    565       TALER_EXCHANGEDB_rollback (TEH_pg);
    566       SET_ERROR_WITH_DETAIL (wc,
    567                              RECOUP_REFRESH_ERROR_INSUFFICIENT_FUNDS,
    568                              insufficient_funds,
    569                              c);
    570       return GNUNET_DB_STATUS_HARD_ERROR;
    571     }
    572   }
    573   return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
    574 }
    575 
    576 
    577 /**
    578  * Run the main DB transaction.
    579  *
    580  * @param[in,out] wc context of the request
    581  */
    582 static void
    583 phase_run_transaction (struct RecoupRefreshContext *wc)
    584 {
    585   enum MHD_Result mhd_ret;
    586   enum GNUNET_GenericReturnValue qs;
    587 
    588   GNUNET_assert (RECOUP_REFRESH_PHASE_RUN_TRANSACTION == wc->phase);
    589   qs = TEH_DB_run_transaction (wc->rc->connection,
    590                                "run recoup-refresh",
    591                                TEH_MT_REQUEST_OTHER,
    592                                &mhd_ret,
    593                                &recoup_transaction,
    594                                wc);
    595   /* If the transaction has changed the phase, we don't alter it. */
    596   if (RECOUP_REFRESH_PHASE_RUN_TRANSACTION != wc->phase)
    597     return;
    598   if (GNUNET_OK != qs)
    599   {
    600     /* persistent soft error, reply already queued */
    601     finish_loop (wc,
    602                  mhd_ret);
    603     return;
    604   }
    605   wc->phase++;
    606 }
    607 
    608 
    609 /**
    610  * Generate the success response: the recouped coins and the batch
    611  * confirmation signature.
    612  *
    613  * @param[in,out] wc context of the request
    614  */
    615 static void
    616 phase_generate_reply_success (struct RecoupRefreshContext *wc)
    617 {
    618   struct TALER_Amount total_amount;
    619   struct GNUNET_HashCode h_recoups;
    620   struct GNUNET_TIME_Timestamp timestamp;
    621   struct TALER_ExchangePublicKeyP pub;
    622   struct TALER_ExchangeSignatureP sig;
    623   json_t *recoups;
    624   enum TALER_ErrorCode ec;
    625 
    626   if (GNUNET_OK !=
    627       TEH_recoup_summarize (wc->request.num_coins,
    628                             wc->request.coins,
    629                             &total_amount,
    630                             &h_recoups,
    631                             &timestamp,
    632                             &recoups))
    633   {
    634     GNUNET_break (0);
    635     SET_ERROR (wc,
    636                RECOUP_REFRESH_ERROR_DB_INVARIANT_FAILURE);
    637     return;
    638   }
    639   ec = TALER_exchange_online_confirm_recoup_refresh_batch_sign (
    640     &TEH_keys_exchange_sign_,
    641     timestamp,
    642     &wc->request.old_coin_pub,
    643     &wc->request.rc,
    644     &total_amount,
    645     &h_recoups,
    646     &pub,
    647     &sig);
    648   if (TALER_EC_NONE != ec)
    649   {
    650     json_decref (recoups);
    651     SET_ERROR_WITH_DETAIL (wc,
    652                            RECOUP_REFRESH_ERROR_CONFIRMATION_SIGN,
    653                            ec_confirmation_sign,
    654                            ec);
    655     return;
    656   }
    657   finish_loop (wc,
    658                TALER_MHD_REPLY_JSON_PACK (
    659                  wc->rc->connection,
    660                  MHD_HTTP_OK,
    661                  GNUNET_JSON_pack_data_auto ("old_coin_pub",
    662                                              &wc->request.old_coin_pub),
    663                  GNUNET_JSON_pack_data_auto ("rc",
    664                                              &wc->request.rc),
    665                  GNUNET_JSON_pack_timestamp ("timestamp",
    666                                              timestamp),
    667                  TALER_JSON_pack_amount ("total_amount",
    668                                          &total_amount),
    669                  GNUNET_JSON_pack_array_steal ("recoups",
    670                                                recoups),
    671                  GNUNET_JSON_pack_data_auto ("exchange_sig",
    672                                              &sig),
    673                  GNUNET_JSON_pack_data_auto ("exchange_pub",
    674                                              &pub)));
    675 }
    676 
    677 
    678 /**
    679  * Report the error in @a wc to the client.
    680  *
    681  * @param[in,out] wc context of the request
    682  */
    683 static void
    684 phase_generate_reply_error (struct RecoupRefreshContext *wc)
    685 {
    686   GNUNET_assert (RECOUP_REFRESH_PHASE_GENERATE_REPLY_ERROR == wc->phase);
    687   switch (wc->error.code)
    688   {
    689   case RECOUP_REFRESH_ERROR_NONE:
    690     break;
    691   case RECOUP_REFRESH_ERROR_BATCH_SIZE_MISMATCH:
    692     finish_loop (wc,
    693                  TALER_MHD_reply_with_error (
    694                    wc->rc->connection,
    695                    MHD_HTTP_BAD_REQUEST,
    696                    TALER_EC_EXCHANGE_RECOUP_REFRESH_BATCH_SIZE_MISMATCH,
    697                    NULL));
    698     return;
    699   case RECOUP_REFRESH_ERROR_BLINDING_FAILED:
    700     finish_loop (wc,
    701                  TALER_MHD_reply_with_error (
    702                    wc->rc->connection,
    703                    MHD_HTTP_INTERNAL_SERVER_ERROR,
    704                    TALER_EC_EXCHANGE_RECOUP_REFRESH_BLINDING_FAILED,
    705                    NULL));
    706     return;
    707   case RECOUP_REFRESH_ERROR_COIN_SIGNATURE_INVALID:
    708     finish_loop (wc,
    709                  TALER_MHD_reply_with_error (
    710                    wc->rc->connection,
    711                    MHD_HTTP_FORBIDDEN,
    712                    TALER_EC_EXCHANGE_RECOUP_REFRESH_SIGNATURE_INVALID,
    713                    NULL));
    714     return;
    715   case RECOUP_REFRESH_ERROR_COMMITMENT_MISMATCH:
    716     finish_loop (wc,
    717                  TALER_MHD_reply_with_error (
    718                    wc->rc->connection,
    719                    MHD_HTTP_CONFLICT,
    720                    TALER_EC_EXCHANGE_RECOUP_REFRESH_COMMITMENT_MISMATCH,
    721                    NULL));
    722     return;
    723   case RECOUP_REFRESH_ERROR_CONFIRMATION_SIGN:
    724     finish_loop (wc,
    725                  TALER_MHD_reply_with_ec (
    726                    wc->rc->connection,
    727                    wc->error.details.ec_confirmation_sign,
    728                    NULL));
    729     return;
    730   case RECOUP_REFRESH_ERROR_DB_FETCH_FAILED:
    731     finish_loop (wc,
    732                  TALER_MHD_reply_with_error (
    733                    wc->rc->connection,
    734                    MHD_HTTP_INTERNAL_SERVER_ERROR,
    735                    TALER_EC_GENERIC_DB_FETCH_FAILED,
    736                    wc->error.details.db_fetch_context));
    737     return;
    738   case RECOUP_REFRESH_ERROR_DB_INVARIANT_FAILURE:
    739     finish_loop (wc,
    740                  TALER_MHD_reply_with_error (
    741                    wc->rc->connection,
    742                    MHD_HTTP_INTERNAL_SERVER_ERROR,
    743                    TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
    744                    NULL));
    745     return;
    746   case RECOUP_REFRESH_ERROR_DENOMINATION_EXPIRED:
    747     finish_loop (wc,
    748                  TEH_RESPONSE_reply_expired_denom_pub_hash (
    749                    wc->rc->connection,
    750                    wc->error.details.denom_h,
    751                    TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
    752                    "RECOUP-REFRESH"));
    753     return;
    754   case RECOUP_REFRESH_ERROR_DENOMINATION_KEY_UNKNOWN:
    755     finish_loop (wc,
    756                  TEH_RESPONSE_reply_unknown_denom_pub_hash (
    757                    wc->rc->connection,
    758                    wc->error.details.denom_h));
    759     return;
    760   case RECOUP_REFRESH_ERROR_DENOMINATION_MISMATCH:
    761     finish_loop (wc,
    762                  TALER_MHD_reply_with_error (
    763                    wc->rc->connection,
    764                    MHD_HTTP_CONFLICT,
    765                    TALER_EC_EXCHANGE_RECOUP_REFRESH_DENOMINATION_MISMATCH,
    766                    NULL));
    767     return;
    768   case RECOUP_REFRESH_ERROR_DENOMINATION_NOT_ELIGIBLE:
    769     finish_loop (wc,
    770                  TEH_RESPONSE_reply_expired_denom_pub_hash (
    771                    wc->rc->connection,
    772                    wc->error.details.denom_h,
    773                    TALER_EC_EXCHANGE_RECOUP_REFRESH_NOT_ELIGIBLE,
    774                    "RECOUP-REFRESH"));
    775     return;
    776   case RECOUP_REFRESH_ERROR_DENOMINATION_SIGNATURE_INVALID:
    777     finish_loop (wc,
    778                  TALER_MHD_reply_with_error (
    779                    wc->rc->connection,
    780                    MHD_HTTP_FORBIDDEN,
    781                    TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID,
    782                    NULL));
    783     return;
    784   case RECOUP_REFRESH_ERROR_DENOMINATION_VALIDITY_IN_FUTURE:
    785     finish_loop (wc,
    786                  TEH_RESPONSE_reply_expired_denom_pub_hash (
    787                    wc->rc->connection,
    788                    wc->error.details.denom_h,
    789                    TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
    790                    "RECOUP-REFRESH"));
    791     return;
    792   case RECOUP_REFRESH_ERROR_INSUFFICIENT_FUNDS:
    793     {
    794       const struct TEH_RecoupCoin *c = wc->error.details.insufficient_funds;
    795 
    796       finish_loop (wc,
    797                    TEH_RESPONSE_reply_coin_insufficient_funds (
    798                      wc->rc->connection,
    799                      TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
    800                      &c->coin.denom_pub_hash,
    801                      &c->coin.coin_pub));
    802       return;
    803     }
    804   case RECOUP_REFRESH_ERROR_KEYS_MISSING:
    805     finish_loop (wc,
    806                  TALER_MHD_reply_with_error (
    807                    wc->rc->connection,
    808                    MHD_HTTP_SERVICE_UNAVAILABLE,
    809                    TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
    810                    NULL));
    811     return;
    812   case RECOUP_REFRESH_ERROR_REQUEST_PARAMETER_MALFORMED:
    813     finish_loop (wc,
    814                  TALER_MHD_reply_with_error (
    815                    wc->rc->connection,
    816                    MHD_HTTP_BAD_REQUEST,
    817                    TALER_EC_GENERIC_PARAMETER_MALFORMED,
    818                    wc->error.details.request_parameter_malformed));
    819     return;
    820   case RECOUP_REFRESH_ERROR_MELT_NOT_FOUND:
    821     finish_loop (wc,
    822                  TALER_MHD_reply_with_error (
    823                    wc->rc->connection,
    824                    MHD_HTTP_NOT_FOUND,
    825                    TALER_EC_EXCHANGE_RECOUP_REFRESH_MELT_NOT_FOUND,
    826                    NULL));
    827     return;
    828   }
    829   GNUNET_break (0);
    830   finish_loop (wc,
    831                MHD_NO);
    832 }
    833 
    834 
    835 /**
    836  * Release the data of a refresh operation as returned by the database.
    837  *
    838  * @param[in,out] rf data to release
    839  */
    840 static void
    841 free_db_refresh_data (struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS *rf)
    842 {
    843   if (NULL != rf->denom_sigs)
    844   {
    845     for (size_t i = 0; i < rf->num_coins; i++)
    846       TALER_blinded_denom_sig_free (&rf->denom_sigs[i]);
    847     GNUNET_free (rf->denom_sigs);
    848   }
    849   GNUNET_free (rf->denom_serials);
    850   GNUNET_free (rf->cs_r_values);
    851   GNUNET_free (rf->transfer_pubs);
    852   GNUNET_free (rf->denom_pub_hashes);
    853 }
    854 
    855 
    856 /**
    857  * Cleanup routine for the request, called upon completion.
    858  *
    859  * @param rc request context to clean up
    860  */
    861 static void
    862 clean_recoup_refresh_rc (struct TEH_RequestContext *rc)
    863 {
    864   struct RecoupRefreshContext *wc = rc->rh_ctx;
    865 
    866   TEH_recoup_free_coins (wc->request.num_coins,
    867                          wc->request.coins);
    868   if (wc->have_refresh)
    869     free_db_refresh_data (&wc->refresh);
    870   GNUNET_free (wc);
    871 }
    872 
    873 
    874 enum MHD_Result
    875 TEH_handler_recoup_refresh (
    876   struct TEH_RequestContext *rc,
    877   const json_t *root,
    878   const char *const args[0])
    879 {
    880   struct RecoupRefreshContext *wc = rc->rh_ctx;
    881 
    882   (void) args;
    883   if (NULL == wc)
    884   {
    885     wc = GNUNET_new (struct RecoupRefreshContext);
    886     rc->rh_ctx = wc;
    887     rc->rh_cleaner = &clean_recoup_refresh_rc;
    888     wc->rc = rc;
    889     wc->now = GNUNET_TIME_timestamp_get ();
    890   }
    891   while (true)
    892   {
    893     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    894                 "recoup-refresh processing in phase %d\n",
    895                 wc->phase);
    896     switch (wc->phase)
    897     {
    898     case RECOUP_REFRESH_PHASE_PARSE:
    899       phase_parse (wc,
    900                    root);
    901       break;
    902     case RECOUP_REFRESH_PHASE_LOOKUP_OPERATION:
    903       phase_lookup_operation (wc);
    904       break;
    905     case RECOUP_REFRESH_PHASE_CHECK_KEYS:
    906       phase_check_keys (wc);
    907       break;
    908     case RECOUP_REFRESH_PHASE_VERIFY_COINS:
    909       phase_verify_coins (wc);
    910       break;
    911     case RECOUP_REFRESH_PHASE_RUN_TRANSACTION:
    912       phase_run_transaction (wc);
    913       break;
    914     case RECOUP_REFRESH_PHASE_GENERATE_REPLY_SUCCESS:
    915       phase_generate_reply_success (wc);
    916       break;
    917     case RECOUP_REFRESH_PHASE_GENERATE_REPLY_ERROR:
    918       phase_generate_reply_error (wc);
    919       break;
    920     case RECOUP_REFRESH_PHASE_RETURN_YES:
    921       return MHD_YES;
    922     case RECOUP_REFRESH_PHASE_RETURN_NO:
    923       return MHD_NO;
    924     }
    925   }
    926 }
    927 
    928 
    929 /* end of taler-exchange-httpd_post-recoup-refresh.c */