merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

taler-merchant-httpd_post-fountain-withdraw.c (38608B)


      1 /*
      2   This file is part of TALER
      3   (C) 2026 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify
      6   it under the terms of the GNU Affero General Public License as
      7   published by the Free Software Foundation; either version 3,
      8   or (at your option) any later version.
      9 
     10   TALER is distributed in the hope that it will be useful, but
     11   WITHOUT ANY WARRANTY; without even the implied warranty of
     12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13   GNU General Public License for more details.
     14 
     15   You should have received a copy of the GNU General Public
     16   License along with TALER; see the file COPYING.  If not,
     17   see <http://www.gnu.org/licenses/>
     18 */
     19 
     20 /**
     21  * @file src/backend/taler-merchant-httpd_post-fountain-withdraw.c
     22  * @brief implementing POST /fountain/withdraw request handling (DD 98)
     23  * @author Bohdan Potuzhnyi
     24  */
     25 #include "platform.h"
     26 #include "taler-merchant-httpd_post-fountain-withdraw.h"
     27 #include "taler-merchant-httpd_token-keys.h"
     28 #include <taler/taler_json_lib.h>
     29 #include "merchant-database/do_fountain_withdraw.h"
     30 #include "merchant-database/get_fountain_by_secret.h"
     31 #include "merchant-database/get_fountain_withdraw.h"
     32 #include "merchant-database/get_token_family.h"
     33 #include "merchant-database/insert_fountain_withdraw_sig.h"
     34 #include "merchant-database/insert_issued_token.h"
     35 #include "merchant-database/iterate_fountain_grants.h"
     36 #include "merchant-database/start.h"
     37 
     38 
     39 /**
     40  * Phases of processing a fountain withdrawal.
     41  */
     42 enum WithdrawPhase
     43 {
     44   /**
     45    * Parse the request envelope.
     46    */
     47   WP_PARSE_REQUEST = 0,
     48 
     49   /**
     50    * Authenticate the fountain credential.
     51    */
     52   WP_AUTHENTICATE,
     53 
     54   /**
     55    * Start the withdrawal transaction.
     56    */
     57   WP_START_TRANSACTION,
     58 
     59   /**
     60    * Lock the fountain and restore any completed withdrawal.
     61    */
     62   WP_CHECK_REPLAY,
     63 
     64   /**
     65    * Load the current grants for a new withdrawal.
     66    */
     67   WP_LOAD_GRANTS,
     68 
     69   /**
     70    * Parse the requested tokens and check their grants.
     71    */
     72   WP_PARSE_ENTRIES,
     73 
     74   /**
     75    * Resolve issue keys and reject duplicate slots.
     76    */
     77   WP_RESOLVE_KEYS,
     78 
     79   /**
     80    * Check and consume the quota for all entries.
     81    */
     82   WP_CONSUME_QUOTA,
     83 
     84   /**
     85    * Sign tokens and store issued-token and replay records.
     86    */
     87   WP_SIGN_TOKENS,
     88 
     89   /**
     90    * Commit the withdrawal before sending its result.
     91    */
     92   WP_COMMIT_TRANSACTION,
     93 
     94   /**
     95    * Return the newly issued or restored signatures.
     96    */
     97   WP_SUCCESS_RESPONSE,
     98 
     99   /**
    100    * Return #MHD_YES to end processing.
    101    */
    102   WP_END_YES,
    103 
    104   /**
    105    * Return #MHD_NO to end processing.
    106    */
    107   WP_END_NO
    108 };
    109 
    110 
    111 /**
    112  * A grant of the fountain the request authenticated as.
    113  */
    114 struct GrantInfo
    115 {
    116   /**
    117    * Slug of the granted token family.
    118    */
    119   char *token_family_slug;
    120 
    121   /**
    122    * Serial of the granted token family.
    123    */
    124   uint64_t token_family_serial;
    125 
    126   /**
    127    * Number of issue-key slots ahead the wallet may withdraw for.
    128    */
    129   uint32_t key_window_size;
    130 };
    131 
    132 
    133 /**
    134  * One entry of the withdraw request being processed.
    135  */
    136 struct WithdrawEntry
    137 {
    138   /**
    139    * Slug of the token family withdrawn from; aliases into the
    140    * request body.
    141    */
    142   const char *token_family_slug;
    143 
    144   /**
    145    * Desired token validity time; selects a key from the grant's window.
    146    */
    147   struct GNUNET_TIME_Timestamp valid_at;
    148 
    149   /**
    150    * Blinded envelopes to sign.
    151    */
    152   struct TALER_TokenEnvelope *envelopes;
    153 
    154   /**
    155    * Length of the @e envelopes array.
    156    */
    157   unsigned int envelopes_len;
    158 
    159   /**
    160    * Serial of the token family.
    161    */
    162   uint64_t token_family_serial;
    163 
    164   /**
    165    * Issue key (and token family details) of the selected slot.
    166    */
    167   struct TALER_MERCHANTDB_TokenFamilyKeyDetails kd;
    168 
    169   /**
    170    * True if @e kd was initialized and must be released.
    171    */
    172   bool have_kd;
    173 };
    174 
    175 
    176 /**
    177  * Request-specific context of a POST /fountain/withdraw request.
    178  */
    179 struct WithdrawContext
    180 {
    181   /**
    182    * Connection to respond on.
    183    */
    184   struct MHD_Connection *connection;
    185 
    186   /**
    187    * Handler context, including the instance and request body.
    188    */
    189   struct TMH_HandlerContext *hc;
    190 
    191   /**
    192    * Current processing phase.
    193    */
    194   enum WithdrawPhase phase;
    195 
    196   /**
    197    * Fountain credential borrowed from the request body.
    198    */
    199   const char *fountain_secret;
    200 
    201   /**
    202    * JSON array of withdrawal entries borrowed from the request body.
    203    */
    204   const json_t *jentries;
    205 
    206   /**
    207    * Canonical hash identifying this withdrawal within the fountain.
    208    */
    209   struct GNUNET_HashCode h_request;
    210 
    211   /**
    212    * Grant results to return after replay or successful commit.
    213    */
    214   json_t *results;
    215 
    216   /**
    217    * True while this synchronous handler owns an open transaction.
    218    */
    219   bool transaction_open;
    220 
    221   /**
    222    * Grants of the fountain the request authenticated as.
    223    */
    224   struct GrantInfo *grants;
    225 
    226   /**
    227    * Length of the @e grants array.
    228    */
    229   unsigned int grants_len;
    230 
    231   /**
    232    * Entries of the request.
    233    */
    234   struct WithdrawEntry *entries;
    235 
    236   /**
    237    * Length of the @e entries array.
    238    */
    239   unsigned int entries_len;
    240 
    241   /**
    242    * Serial of the fountain the request authenticated as.
    243    */
    244   uint64_t fountain_serial;
    245 
    246   /**
    247    * Time this request is processed at.
    248    */
    249   struct GNUNET_TIME_Timestamp now;
    250 };
    251 
    252 
    253 /**
    254  * Release the request-specific context.
    255  *
    256  * @param cls a `struct WithdrawContext *`
    257  */
    258 static void
    259 withdraw_context_cleanup (void *cls)
    260 {
    261   struct WithdrawContext *wc = cls;
    262 
    263   for (unsigned int i = 0; i < wc->grants_len; i++)
    264     GNUNET_free (wc->grants[i].token_family_slug);
    265   GNUNET_array_grow (wc->grants,
    266                      wc->grants_len,
    267                      0);
    268   /* @e entries is only allocated once the request body was parsed;
    269      @e entries_len is known before that. */
    270   for (unsigned int i = 0; (NULL != wc->entries) && (i < wc->entries_len); i++)
    271   {
    272     struct WithdrawEntry *we = &wc->entries[i];
    273 
    274     for (unsigned int j = 0; j < we->envelopes_len; j++)
    275       if (NULL != we->envelopes[j].blinded_pub)
    276         GNUNET_CRYPTO_blinded_message_decref (we->envelopes[j].blinded_pub);
    277     GNUNET_free (we->envelopes);
    278     if (we->have_kd)
    279       TMH_token_key_details_free (&we->kd);
    280   }
    281   GNUNET_free (wc->entries);
    282   json_decref (wc->results);
    283   GNUNET_free (wc);
    284 }
    285 
    286 
    287 /**
    288  * Add a grant of the fountain to the context in @a cls.
    289  *
    290  * @param cls a `struct WithdrawContext *`
    291  * @param token_family_slug slug of the granted token family
    292  * @param token_family_serial serial of the granted token family
    293  * @param tokens_per_period_limit maximum withdrawals per period
    294  * @param tokens_per_period_stash suggested tokens to hold per period
    295  * @param key_window_size number of slots ahead withdrawals are allowed
    296  */
    297 static void
    298 add_grant (void *cls,
    299            const char *token_family_slug,
    300            uint64_t token_family_serial,
    301            uint64_t tokens_per_period_limit,
    302            uint64_t tokens_per_period_stash,
    303            uint32_t key_window_size)
    304 {
    305   struct WithdrawContext *wc = cls;
    306   struct GrantInfo gi = {
    307     .token_family_slug = GNUNET_strdup (token_family_slug),
    308     .token_family_serial = token_family_serial,
    309     .key_window_size = key_window_size
    310   };
    311 
    312   (void) tokens_per_period_limit;
    313   (void) tokens_per_period_stash;
    314   GNUNET_array_append (wc->grants,
    315                        wc->grants_len,
    316                        gi);
    317 }
    318 
    319 
    320 /**
    321  * Find the grant for @a slug among the grants of the fountain.
    322  *
    323  * @param wc context to search
    324  * @param slug token family slug to look for
    325  * @return NULL if the fountain does not grant @a slug
    326  */
    327 static const struct GrantInfo *
    328 find_grant (const struct WithdrawContext *wc,
    329             const char *slug)
    330 {
    331   for (unsigned int i = 0; i < wc->grants_len; i++)
    332     if (0 == strcmp (wc->grants[i].token_family_slug,
    333                      slug))
    334       return &wc->grants[i];
    335   return NULL;
    336 }
    337 
    338 
    339 /**
    340  * Authenticate the request using the supplied fountain secret.
    341  *
    342  * @param[in,out] wc context to initialize
    343  * @return #GNUNET_OK on success, #GNUNET_NO if an error response was
    344  *         queued, #GNUNET_SYSERR on hard failure
    345  */
    346 static enum GNUNET_GenericReturnValue
    347 phase_authenticate (struct WithdrawContext *wc)
    348 {
    349   struct MHD_Connection *connection = wc->connection;
    350   const char *instance_id = wc->hc->instance->settings.id;
    351   const char *fountain_secret = wc->fountain_secret;
    352   struct GNUNET_HashCode h_secret;
    353   struct GNUNET_TIME_Relative poll_freq;
    354   enum GNUNET_DB_QueryStatus qs;
    355 
    356   {
    357     char secret[32];
    358 
    359     if (GNUNET_OK !=
    360         GNUNET_STRINGS_string_to_data (fountain_secret,
    361                                        strlen (fountain_secret),
    362                                        secret,
    363                                        sizeof (secret)))
    364     {
    365       /* Reply as for an unknown credential, so that the endpoint does
    366          not become an oracle for the shape of valid secrets. */
    367       GNUNET_break_op (0);
    368       return (MHD_YES ==
    369               TALER_MHD_reply_with_error (connection,
    370                                           MHD_HTTP_UNAUTHORIZED,
    371                                           TALER_EC_MERCHANT_GENERIC_UNAUTHORIZED,
    372                                           "fountain secret"))
    373              ? GNUNET_NO
    374              : GNUNET_SYSERR;
    375     }
    376     GNUNET_CRYPTO_hash (secret,
    377                         sizeof (secret),
    378                         &h_secret);
    379     memset (secret,
    380             0,
    381             sizeof (secret));
    382   }
    383   qs = TALER_MERCHANTDB_get_fountain_by_secret (TMH_db,
    384                                                 instance_id,
    385                                                 &h_secret,
    386                                                 &wc->fountain_serial,
    387                                                 &poll_freq);
    388   if (0 > qs)
    389   {
    390     GNUNET_break (0);
    391     return (MHD_YES ==
    392             TALER_MHD_reply_with_error (connection,
    393                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
    394                                         TALER_EC_GENERIC_DB_FETCH_FAILED,
    395                                         "get_fountain_by_secret"))
    396            ? GNUNET_NO
    397            : GNUNET_SYSERR;
    398   }
    399   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    400     return (MHD_YES ==
    401             TALER_MHD_reply_with_error (connection,
    402                                         MHD_HTTP_UNAUTHORIZED,
    403                                         TALER_EC_MERCHANT_GENERIC_UNAUTHORIZED,
    404                                         "fountain secret"))
    405            ? GNUNET_NO
    406            : GNUNET_SYSERR;
    407   wc->phase = WP_START_TRANSACTION;
    408   return GNUNET_OK;
    409 }
    410 
    411 
    412 /**
    413  * Load current grants after locking the fountain and checking for a replay.
    414  *
    415  * @param[in,out] wc context to populate
    416  * @return #GNUNET_OK on success, #GNUNET_NO if an error was queued,
    417  *         #GNUNET_SYSERR if queueing failed
    418  */
    419 static enum GNUNET_GenericReturnValue
    420 phase_load_grants (struct WithdrawContext *wc)
    421 {
    422   struct MHD_Connection *connection = wc->connection;
    423   const char *instance_id = wc->hc->instance->settings.id;
    424   enum GNUNET_DB_QueryStatus qs;
    425 
    426   qs = TALER_MERCHANTDB_iterate_fountain_grants (TMH_db,
    427                                                  instance_id,
    428                                                  wc->fountain_serial,
    429                                                  &add_grant,
    430                                                  wc);
    431   if (0 > qs)
    432   {
    433     GNUNET_break (0);
    434     return (MHD_YES ==
    435             TALER_MHD_reply_with_error (connection,
    436                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
    437                                         TALER_EC_GENERIC_DB_FETCH_FAILED,
    438                                         "iterate_fountain_grants"))
    439            ? GNUNET_NO
    440            : GNUNET_SYSERR;
    441   }
    442   wc->phase = WP_PARSE_ENTRIES;
    443   return GNUNET_OK;
    444 }
    445 
    446 
    447 /**
    448  * Parse the envelopes of one request entry.
    449  *
    450  * @param connection connection to report errors on
    451  * @param jenvelopes JSON array of token envelopes
    452  * @param total_envelopes total number of envelopes parsed so far
    453  * @param[in,out] we entry to complete
    454  * @return #GNUNET_OK on success, #GNUNET_NO if an error response was
    455  *         queued, #GNUNET_SYSERR on hard failure
    456  */
    457 static enum GNUNET_GenericReturnValue
    458 parse_envelopes (struct MHD_Connection *connection,
    459                  const json_t *jenvelopes,
    460                  unsigned int total_envelopes,
    461                  struct WithdrawEntry *we)
    462 {
    463   unsigned int len = (unsigned int) json_array_size (jenvelopes);
    464   size_t idx;
    465   json_t *jev;
    466 
    467   if ( (0 == len) ||
    468        (total_envelopes + len > TMH_MAX_FOUNTAIN_ENVELOPES) )
    469   {
    470     GNUNET_break_op (0);
    471     return (MHD_YES ==
    472             TALER_MHD_reply_with_error (connection,
    473                                         MHD_HTTP_BAD_REQUEST,
    474                                         TALER_EC_GENERIC_PARAMETER_MALFORMED,
    475                                         "'envelopes' empty or too many envelopes"))
    476            ? GNUNET_NO
    477            : GNUNET_SYSERR;
    478   }
    479   we->envelopes = GNUNET_new_array (len,
    480                                     struct TALER_TokenEnvelope);
    481   we->envelopes_len = len;
    482   json_array_foreach ((json_t *) jenvelopes, idx, jev)
    483   {
    484     struct GNUNET_JSON_Specification ispec[] = {
    485       TALER_JSON_spec_token_envelope (NULL,
    486                                       &we->envelopes[idx]),
    487       GNUNET_JSON_spec_end ()
    488     };
    489     enum GNUNET_GenericReturnValue res;
    490 
    491     res = TALER_MHD_parse_json_data (connection,
    492                                      jev,
    493                                      ispec);
    494     if (GNUNET_OK != res)
    495     {
    496       GNUNET_break_op (0);
    497       return res;
    498     }
    499   }
    500   return GNUNET_OK;
    501 }
    502 
    503 
    504 /**
    505  * Parse the entries of the request into @a wc, checking each against
    506  * the grants of the fountain. Resolve keys after all envelopes are parsed.
    507  *
    508  * @param[in,out] wc context to complete
    509  * @return #GNUNET_OK on success, #GNUNET_NO if an error response was
    510  *         queued, #GNUNET_SYSERR on hard failure
    511  */
    512 static enum GNUNET_GenericReturnValue
    513 phase_parse_entries (struct WithdrawContext *wc)
    514 {
    515   struct MHD_Connection *connection = wc->connection;
    516   const json_t *jentries = wc->jentries;
    517   unsigned int total_envelopes = 0;
    518   size_t idx;
    519   json_t *jentry;
    520 
    521   wc->entries = GNUNET_new_array (wc->entries_len,
    522                                   struct WithdrawEntry);
    523   json_array_foreach ((json_t *) jentries, idx, jentry)
    524   {
    525     struct WithdrawEntry *we = &wc->entries[idx];
    526     const json_t *jenvelopes;
    527     const struct GrantInfo *gi;
    528     enum GNUNET_GenericReturnValue res;
    529     struct GNUNET_JSON_Specification espec[] = {
    530       GNUNET_JSON_spec_string ("token_family_slug",
    531                                &we->token_family_slug),
    532       GNUNET_JSON_spec_mark_optional (
    533         GNUNET_JSON_spec_timestamp ("valid_at",
    534                                     &we->valid_at),
    535         NULL),
    536       GNUNET_JSON_spec_array_const ("envelopes",
    537                                     &jenvelopes),
    538       GNUNET_JSON_spec_end ()
    539     };
    540 
    541     we->valid_at = wc->now;
    542     res = TALER_MHD_parse_json_data (connection,
    543                                      jentry,
    544                                      espec);
    545     if (GNUNET_OK != res)
    546     {
    547       GNUNET_break_op (0);
    548       return res;
    549     }
    550     gi = find_grant (wc,
    551                      we->token_family_slug);
    552     if (NULL == gi)
    553     {
    554       GNUNET_break_op (0);
    555       return (MHD_YES ==
    556               TALER_MHD_reply_with_error (
    557                 connection,
    558                 MHD_HTTP_CONFLICT,
    559                 TALER_EC_MERCHANT_POST_FOUNTAIN_WITHDRAW_GRANT_UNKNOWN,
    560                 we->token_family_slug))
    561              ? GNUNET_NO
    562              : GNUNET_SYSERR;
    563     }
    564     we->token_family_serial = gi->token_family_serial;
    565     res = parse_envelopes (connection,
    566                            jenvelopes,
    567                            total_envelopes,
    568                            we);
    569     if (GNUNET_OK != res)
    570       return res;
    571     total_envelopes += we->envelopes_len;
    572   }
    573   wc->phase = WP_RESOLVE_KEYS;
    574   return GNUNET_OK;
    575 }
    576 
    577 
    578 /**
    579  * Select each entry's key from the same expiry-based window advertised
    580  * by GET /fountain/info, and reject duplicate (token family, key) pairs.
    581  *
    582  * @param[in,out] wc context to complete
    583  * @return #GNUNET_OK on success, #GNUNET_NO if an error response was
    584  *         queued, #GNUNET_SYSERR on hard failure
    585  */
    586 static enum GNUNET_GenericReturnValue
    587 phase_resolve_keys (struct WithdrawContext *wc)
    588 {
    589   struct MHD_Connection *connection = wc->connection;
    590   const char *instance_id = wc->hc->instance->settings.id;
    591 
    592   for (unsigned int i = 0; i < wc->entries_len; i++)
    593   {
    594     struct WithdrawEntry *we = &wc->entries[i];
    595     const struct GrantInfo *gi;
    596     struct TALER_MERCHANTDB_TokenFamilyDetails tf;
    597     struct TMH_TokenKeyWindow window;
    598     unsigned int key_index = 0;
    599     enum GNUNET_GenericReturnValue res;
    600     enum GNUNET_DB_QueryStatus qs;
    601 
    602     gi = find_grant (wc,
    603                      we->token_family_slug);
    604     GNUNET_assert (NULL != gi);
    605     qs = TALER_MERCHANTDB_get_token_family (TMH_db,
    606                                             instance_id,
    607                                             we->token_family_slug,
    608                                             &tf);
    609     if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
    610     {
    611       GNUNET_break (0);
    612       return (MHD_YES ==
    613               TALER_MHD_reply_with_error (connection,
    614                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
    615                                           TALER_EC_GENERIC_DB_FETCH_FAILED,
    616                                           "get_token_family"))
    617              ? GNUNET_NO
    618              : GNUNET_SYSERR;
    619     }
    620     res = TMH_token_key_window_get (connection,
    621                                     instance_id,
    622                                     &tf,
    623                                     wc->now,
    624                                     gi->key_window_size,
    625                                     &window);
    626     TALER_MERCHANTDB_token_family_details_free (&tf);
    627     if (GNUNET_OK != res)
    628       return res;
    629     if ( (0 == window.keys_len) ||
    630          (GNUNET_OK !=
    631           TMH_token_key_window_find (&window,
    632                                      we->valid_at,
    633                                      &key_index)) )
    634     {
    635       TMH_token_key_window_free (&window);
    636       return (MHD_YES ==
    637               TALER_MHD_reply_with_error (
    638                 connection,
    639                 MHD_HTTP_CONFLICT,
    640                 TALER_EC_MERCHANT_POST_FOUNTAIN_WITHDRAW_SLOT_OUTSIDE_WINDOW,
    641                 we->token_family_slug))
    642              ? GNUNET_NO
    643              : GNUNET_SYSERR;
    644     }
    645     /* Transfer the selected key, rather than looking it up again by a
    646        timestamp that an overlapping earlier key could also cover. */
    647     we->kd = window.keys[key_index];
    648     memset (&window.keys[key_index],
    649             0,
    650             sizeof (window.keys[key_index]));
    651     we->have_kd = true;
    652     TMH_token_key_window_free (&window);
    653     if (NULL == we->kd.priv.private_key)
    654     {
    655       GNUNET_break (0);
    656       return (MHD_YES ==
    657               TALER_MHD_reply_with_error (
    658                 connection,
    659                 MHD_HTTP_INTERNAL_SERVER_ERROR,
    660                 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
    661                 "issue private key unavailable"))
    662              ? GNUNET_NO
    663              : GNUNET_SYSERR;
    664     }
    665     for (unsigned int j = 0; j < we->envelopes_len; j++)
    666     {
    667       if (we->envelopes[j].blinded_pub->cipher !=
    668           we->kd.priv.private_key->cipher)
    669       {
    670         GNUNET_break_op (0);
    671         return (MHD_YES ==
    672                 TALER_MHD_reply_with_error (
    673                   connection,
    674                   MHD_HTTP_BAD_REQUEST,
    675                   TALER_EC_GENERIC_PARAMETER_MALFORMED,
    676                   "envelope cipher does not match issue key"))
    677                ? GNUNET_NO
    678                : GNUNET_SYSERR;
    679       }
    680     }
    681     for (unsigned int j = 0; j < i; j++)
    682     {
    683       if ( (wc->entries[j].token_family_serial ==
    684             we->token_family_serial) &&
    685            (GNUNET_TIME_timestamp_cmp (
    686               wc->entries[j].kd.signature_validity_start,
    687               ==,
    688               we->kd.signature_validity_start)) )
    689       {
    690         GNUNET_break_op (0);
    691         return (MHD_YES ==
    692                 TALER_MHD_reply_with_error (
    693                   connection,
    694                   MHD_HTTP_BAD_REQUEST,
    695                   TALER_EC_GENERIC_PARAMETER_MALFORMED,
    696                   "multiple entries for one token family and key slot"))
    697                ? GNUNET_NO
    698                : GNUNET_SYSERR;
    699       }
    700     }
    701   }
    702   wc->phase = WP_CONSUME_QUOTA;
    703   return GNUNET_OK;
    704 }
    705 
    706 
    707 /**
    708  * Atomically check and consume the per-period withdrawal quota of
    709  * all entries.  Either the whole request fits, or nothing is
    710  * consumed.
    711  *
    712  * @param[in,out] wc context of the request
    713  * @return #GNUNET_OK on success, #GNUNET_NO if an error response was
    714  *         queued, #GNUNET_SYSERR on hard failure
    715  */
    716 static enum GNUNET_GenericReturnValue
    717 phase_consume_quota (struct WithdrawContext *wc)
    718 {
    719   struct MHD_Connection *connection = wc->connection;
    720   const char *instance_id = wc->hc->instance->settings.id;
    721   uint64_t family_serials[GNUNET_NZL (wc->entries_len)];
    722   struct GNUNET_TIME_Timestamp slot_starts[GNUNET_NZL (wc->entries_len)];
    723   uint64_t counts[GNUNET_NZL (wc->entries_len)];
    724   unsigned int failed_index;
    725   bool no_grant;
    726   bool exceeded;
    727   enum GNUNET_DB_QueryStatus qs;
    728 
    729   for (unsigned int i = 0; i < wc->entries_len; i++)
    730   {
    731     family_serials[i] = wc->entries[i].token_family_serial;
    732     slot_starts[i] = wc->entries[i].kd.signature_validity_start;
    733     counts[i] = wc->entries[i].envelopes_len;
    734   }
    735   qs = TALER_MERCHANTDB_do_fountain_withdraw (TMH_db,
    736                                               instance_id,
    737                                               wc->fountain_serial,
    738                                               wc->entries_len,
    739                                               family_serials,
    740                                               slot_starts,
    741                                               counts,
    742                                               &failed_index,
    743                                               &no_grant,
    744                                               &exceeded);
    745   if (0 > qs)
    746   {
    747     GNUNET_break (0);
    748     return (MHD_YES ==
    749             TALER_MHD_reply_with_error (connection,
    750                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
    751                                         TALER_EC_GENERIC_DB_STORE_FAILED,
    752                                         "do_fountain_withdraw"))
    753            ? GNUNET_NO
    754            : GNUNET_SYSERR;
    755   }
    756   if (exceeded)
    757     return (MHD_YES ==
    758             TALER_MHD_reply_with_error (
    759               connection,
    760               MHD_HTTP_TOO_MANY_REQUESTS,
    761               TALER_EC_MERCHANT_POST_FOUNTAIN_WITHDRAW_LIMIT_EXCEEDED,
    762               wc->entries[failed_index].token_family_slug))
    763            ? GNUNET_NO
    764            : GNUNET_SYSERR;
    765   if (no_grant)
    766     return (MHD_YES ==
    767             TALER_MHD_reply_with_error (
    768               connection,
    769               MHD_HTTP_CONFLICT,
    770               TALER_EC_MERCHANT_POST_FOUNTAIN_WITHDRAW_GRANT_UNKNOWN,
    771               wc->entries[failed_index].token_family_slug))
    772            ? GNUNET_NO
    773            : GNUNET_SYSERR;
    774   wc->phase = WP_SIGN_TOKENS;
    775   return GNUNET_OK;
    776 }
    777 
    778 
    779 /**
    780  * Blind-sign the envelopes of all entries, record the issued tokens
    781  * and construct the grant results. The caller must commit the quota,
    782  * issued-token records and replay results together before responding.
    783  *
    784  * @param[in,out] wc context of the request
    785  * @return #GNUNET_OK on success, #GNUNET_NO if an error was queued,
    786  *         #GNUNET_SYSERR if queueing failed
    787  */
    788 static enum GNUNET_GenericReturnValue
    789 phase_sign_tokens (struct WithdrawContext *wc)
    790 {
    791   struct MHD_Connection *connection = wc->connection;
    792   const struct GNUNET_HashCode *h_request = &wc->h_request;
    793   json_t *jresults;
    794 
    795   jresults = json_array ();
    796   GNUNET_assert (NULL != jresults);
    797   for (unsigned int i = 0; i < wc->entries_len; i++)
    798   {
    799     const struct WithdrawEntry *we = &wc->entries[i];
    800     struct TALER_TokenIssuePublicKeyHashP h_issue = {
    801       .hash = we->kd.pub.public_key->pub_key_hash
    802     };
    803     json_t *jsigs;
    804 
    805     jsigs = json_array ();
    806     GNUNET_assert (NULL != jsigs);
    807     for (unsigned int j = 0; j < we->envelopes_len; j++)
    808     {
    809       struct TALER_BlindedTokenIssueSignature sig;
    810       enum GNUNET_DB_QueryStatus qs;
    811       bool no_family;
    812 
    813       TALER_token_issue_sign (&we->kd.priv,
    814                               &we->envelopes[j],
    815                               &sig);
    816       if (NULL == sig.signature)
    817       {
    818         GNUNET_break (0);
    819         json_decref (jsigs);
    820         json_decref (jresults);
    821         return (MHD_YES == TALER_MHD_reply_with_error (
    822                   connection,
    823                   MHD_HTTP_INTERNAL_SERVER_ERROR,
    824                   TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
    825                   "token_issue_sign"))
    826           ? GNUNET_NO
    827           : GNUNET_SYSERR;
    828       }
    829       qs = TALER_MERCHANTDB_insert_issued_token (TMH_db,
    830                                                  NULL,
    831                                                  &h_issue,
    832                                                  &sig,
    833                                                  &no_family);
    834       if (0 > qs)
    835       {
    836         GNUNET_break (0);
    837         GNUNET_CRYPTO_blinded_sig_decref (sig.signature);
    838         json_decref (jsigs);
    839         json_decref (jresults);
    840         return (MHD_YES == TALER_MHD_reply_with_error (connection,
    841                                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
    842                                                        TALER_EC_GENERIC_DB_STORE_FAILED,
    843                                                        "insert_issued_token"))
    844           ? GNUNET_NO
    845           : GNUNET_SYSERR;
    846       }
    847       if (no_family)
    848       {
    849         /* The token family key was deleted after resolving the grant,
    850            so we cannot issue this token anymore. */
    851         GNUNET_break_op (0);
    852         GNUNET_CRYPTO_blinded_sig_decref (sig.signature);
    853         json_decref (jsigs);
    854         json_decref (jresults);
    855         return (MHD_YES == TALER_MHD_reply_with_error (connection,
    856                                                        MHD_HTTP_NOT_FOUND,
    857                                                        TALER_EC_MERCHANT_GENERIC_TOKEN_KEY_UNKNOWN,
    858                                                        NULL))
    859           ? GNUNET_NO
    860           : GNUNET_SYSERR;
    861       }
    862       qs = TALER_MERCHANTDB_insert_fountain_withdraw_sig (TMH_db,
    863                                                           wc->fountain_serial,
    864                                                           h_request,
    865                                                           i,
    866                                                           j,
    867                                                           &h_issue,
    868                                                           &sig);
    869       if (0 >= qs)
    870       {
    871         GNUNET_break (0);
    872         GNUNET_CRYPTO_blinded_sig_decref (sig.signature);
    873         json_decref (jsigs);
    874         json_decref (jresults);
    875         return (MHD_YES == TALER_MHD_reply_with_error (
    876                   connection,
    877                   MHD_HTTP_INTERNAL_SERVER_ERROR,
    878                   TALER_EC_GENERIC_DB_STORE_FAILED,
    879                   "insert_fountain_withdraw_sig"))
    880           ? GNUNET_NO
    881           : GNUNET_SYSERR;
    882       }
    883       GNUNET_assert (0 ==
    884                      json_array_append_new (
    885                        jsigs,
    886                        GNUNET_JSON_PACK (
    887                          GNUNET_JSON_pack_blinded_sig ("blind_sig",
    888                                                        sig.signature))));
    889       GNUNET_CRYPTO_blinded_sig_decref (sig.signature);
    890     }
    891     GNUNET_assert (0 ==
    892                    json_array_append_new (
    893                      jresults,
    894                      GNUNET_JSON_PACK (
    895                        GNUNET_JSON_pack_string ("token_family_slug",
    896                                                 we->token_family_slug),
    897                        GNUNET_JSON_pack_data_auto ("h_issue",
    898                                                    &h_issue),
    899                        GNUNET_JSON_pack_array_steal ("token_sigs",
    900                                                      jsigs))));
    901   }
    902   wc->results = jresults;
    903   wc->phase = WP_COMMIT_TRANSACTION;
    904   return GNUNET_OK;
    905 }
    906 
    907 
    908 /**
    909  * Closure for #restore_grant_cb().
    910  */
    911 struct RestoreState
    912 {
    913   /**
    914    * Grant results rebuilt so far.
    915    */
    916   json_t *grants;
    917 
    918   /**
    919    * Signatures of the grant currently being rebuilt.
    920    */
    921   json_t *sigs;
    922 
    923   /**
    924    * Slug of that grant.
    925    */
    926   char *slug;
    927 
    928   /**
    929    * Issue key hash of that grant.
    930    */
    931   struct TALER_TokenIssuePublicKeyHashP h_issue;
    932 
    933   /**
    934    * Index of that grant in the original request.
    935    */
    936   uint32_t grant_index;
    937 
    938   /**
    939    * True once a grant has been started, so @e slug and @e h_issue
    940    * are meaningful.
    941    */
    942   bool started;
    943 };
    944 
    945 
    946 /**
    947  * Append the grant currently being rebuilt to the results.
    948  *
    949  * @param[in,out] rs state to flush
    950  */
    951 static void
    952 flush_grant (struct RestoreState *rs)
    953 {
    954   if (! rs->started)
    955     return;
    956   GNUNET_assert (0 ==
    957                  json_array_append_new (
    958                    rs->grants,
    959                    GNUNET_JSON_PACK (
    960                      GNUNET_JSON_pack_string ("token_family_slug",
    961                                               rs->slug),
    962                      GNUNET_JSON_pack_data_auto ("h_issue",
    963                                                  &rs->h_issue),
    964                      GNUNET_JSON_pack_array_steal ("token_sigs",
    965                                                    rs->sigs))));
    966   GNUNET_free (rs->slug);
    967   rs->sigs = NULL;
    968   rs->started = false;
    969 }
    970 
    971 
    972 /**
    973  * Rebuild the response of an earlier withdrawal from its stored
    974  * signatures.  Rows arrive in response order, so a change of
    975  * @a grant_index closes the grant being assembled.
    976  *
    977  * @param cls a `struct RestoreState *`
    978  * @param grant_index offset of the grant in the original request
    979  * @param token_family_slug token family of that grant
    980  * @param h_issue issue key the signature was made with
    981  * @param blind_sig the blind signature handed out originally
    982  */
    983 static void
    984 restore_grant_cb (void *cls,
    985                   uint32_t grant_index,
    986                   const char *token_family_slug,
    987                   const struct TALER_TokenIssuePublicKeyHashP *h_issue,
    988                   const struct GNUNET_CRYPTO_BlindedSignature *blind_sig)
    989 {
    990   struct RestoreState *rs = cls;
    991 
    992   if ( (! rs->started) ||
    993        (grant_index != rs->grant_index) )
    994   {
    995     flush_grant (rs);
    996     rs->sigs = json_array ();
    997     GNUNET_assert (NULL != rs->sigs);
    998     rs->slug = GNUNET_strdup (token_family_slug);
    999     rs->h_issue = *h_issue;
   1000     rs->grant_index = grant_index;
   1001     rs->started = true;
   1002   }
   1003   GNUNET_assert (0 ==
   1004                  json_array_append_new (
   1005                    rs->sigs,
   1006                    GNUNET_JSON_PACK (
   1007                      GNUNET_JSON_pack_blinded_sig (
   1008                        "blind_sig",
   1009                        (struct GNUNET_CRYPTO_BlindedSignature *) blind_sig))));
   1010 }
   1011 
   1012 
   1013 /**
   1014  * Parse the top-level request without inspecting the current grants.
   1015  *
   1016  * @param[in,out] wc request context
   1017  * @return #GNUNET_OK to continue, #GNUNET_NO if an error was queued,
   1018  *         #GNUNET_SYSERR if queueing failed
   1019  */
   1020 static enum GNUNET_GenericReturnValue
   1021 phase_parse_request (struct WithdrawContext *wc)
   1022 {
   1023   struct MHD_Connection *connection = wc->connection;
   1024   struct GNUNET_JSON_Specification spec[] = {
   1025     GNUNET_JSON_spec_string ("fountain_secret",
   1026                              &wc->fountain_secret),
   1027     GNUNET_JSON_spec_array_const ("grants",
   1028                                   &wc->jentries),
   1029     GNUNET_JSON_spec_end ()
   1030   };
   1031   enum GNUNET_GenericReturnValue res;
   1032 
   1033   res = TALER_MHD_parse_json_data (connection,
   1034                                    wc->hc->request_body,
   1035                                    spec);
   1036   if (GNUNET_OK != res)
   1037   {
   1038     GNUNET_break_op (0);
   1039     return res;
   1040   }
   1041   {
   1042     size_t len = json_array_size (wc->jentries);
   1043 
   1044     if (len > TMH_MAX_FOUNTAIN_ENVELOPES)
   1045     {
   1046       GNUNET_break_op (0);
   1047       return (MHD_YES == TALER_MHD_reply_with_error (
   1048                 connection,
   1049                 MHD_HTTP_BAD_REQUEST,
   1050                 TALER_EC_GENERIC_PARAMETER_MALFORMED,
   1051                 "'grants' array too long"))
   1052         ? GNUNET_NO
   1053         : GNUNET_SYSERR;
   1054     }
   1055     wc->entries_len = (unsigned int) len;
   1056   }
   1057   wc->phase = WP_AUTHENTICATE;
   1058   return GNUNET_OK;
   1059 }
   1060 
   1061 
   1062 /**
   1063  * Start the transaction covering replay, quotas and token issuance.
   1064  *
   1065  * @param[in,out] wc request context
   1066  * @return #GNUNET_OK to continue, #GNUNET_NO if an error was queued,
   1067  *         #GNUNET_SYSERR if queueing failed
   1068  */
   1069 static enum GNUNET_GenericReturnValue
   1070 phase_start_transaction (struct WithdrawContext *wc)
   1071 {
   1072   TALER_json_hash (wc->jentries,
   1073                    &wc->h_request);
   1074   if (GNUNET_OK != TALER_MERCHANTDB_start_read_committed (
   1075         TMH_db,
   1076         "fountain withdraw"))
   1077     return (MHD_YES == TALER_MHD_reply_with_error (
   1078               wc->connection,
   1079               MHD_HTTP_INTERNAL_SERVER_ERROR,
   1080               TALER_EC_GENERIC_DB_START_FAILED,
   1081               NULL))
   1082       ? GNUNET_NO
   1083       : GNUNET_SYSERR;
   1084   wc->transaction_open = true;
   1085   wc->phase = WP_CHECK_REPLAY;
   1086   return GNUNET_OK;
   1087 }
   1088 
   1089 
   1090 /**
   1091  * Lock the fountain and restore a completed withdrawal, if any.
   1092  *
   1093  * @param[in,out] wc request context
   1094  * @return #GNUNET_OK to continue, #GNUNET_NO if an error was queued,
   1095  *         #GNUNET_SYSERR if queueing failed
   1096  */
   1097 static enum GNUNET_GenericReturnValue
   1098 phase_check_replay (struct WithdrawContext *wc)
   1099 {
   1100   struct RestoreState rs = { 0 };
   1101   enum GNUNET_DB_QueryStatus qs;
   1102   bool not_found;
   1103 
   1104   /* Serialize withdrawals of this fountain. Replay before consulting
   1105      grants or key windows, which may have changed since the first call. */
   1106   rs.grants = json_array ();
   1107   GNUNET_assert (NULL != rs.grants);
   1108   qs = TALER_MERCHANTDB_get_fountain_withdraw (TMH_db,
   1109                                                wc->fountain_serial,
   1110                                                &wc->h_request,
   1111                                                &not_found,
   1112                                                &restore_grant_cb,
   1113                                                &rs);
   1114   flush_grant (&rs);
   1115   if (qs < 0)
   1116   {
   1117     json_decref (rs.grants);
   1118     return (MHD_YES == TALER_MHD_reply_with_error (
   1119               wc->connection,
   1120               MHD_HTTP_INTERNAL_SERVER_ERROR,
   1121               TALER_EC_GENERIC_DB_FETCH_FAILED,
   1122               "get_fountain_withdraw"))
   1123       ? GNUNET_NO
   1124       : GNUNET_SYSERR;
   1125   }
   1126   if (not_found)
   1127   {
   1128     json_decref (rs.grants);
   1129     return (MHD_YES == TALER_MHD_reply_with_error (
   1130               wc->connection,
   1131               MHD_HTTP_UNAUTHORIZED,
   1132               TALER_EC_MERCHANT_GENERIC_UNAUTHORIZED,
   1133               "fountain secret"))
   1134       ? GNUNET_NO
   1135       : GNUNET_SYSERR;
   1136   }
   1137   if (0 != json_array_size (rs.grants))
   1138   {
   1139     TALER_MERCHANTDB_rollback (TMH_db);
   1140     wc->transaction_open = false;
   1141     wc->results = rs.grants;
   1142     wc->phase = WP_SUCCESS_RESPONSE;
   1143     return GNUNET_OK;
   1144   }
   1145   json_decref (rs.grants);
   1146   wc->phase = WP_LOAD_GRANTS;
   1147   return GNUNET_OK;
   1148 }
   1149 
   1150 
   1151 /**
   1152  * Commit quota, issued tokens and replay records together.
   1153  *
   1154  * @param[in,out] wc request context
   1155  * @return #GNUNET_OK to continue, #GNUNET_NO if an error was queued,
   1156  *         #GNUNET_SYSERR if queueing failed
   1157  */
   1158 static enum GNUNET_GenericReturnValue
   1159 phase_commit_transaction (struct WithdrawContext *wc)
   1160 {
   1161   enum GNUNET_DB_QueryStatus qs;
   1162 
   1163   qs = TALER_MERCHANTDB_commit (TMH_db);
   1164   if (qs < 0)
   1165   {
   1166     return (MHD_YES == TALER_MHD_reply_with_error (
   1167               wc->connection,
   1168               MHD_HTTP_INTERNAL_SERVER_ERROR,
   1169               TALER_EC_GENERIC_DB_COMMIT_FAILED,
   1170               NULL))
   1171       ? GNUNET_NO
   1172       : GNUNET_SYSERR;
   1173   }
   1174   wc->transaction_open = false;
   1175   wc->phase = WP_SUCCESS_RESPONSE;
   1176   return GNUNET_OK;
   1177 }
   1178 
   1179 
   1180 /**
   1181  * Return the newly issued or restored grant results.
   1182  *
   1183  * @param[in,out] wc request context
   1184  * @return #GNUNET_NO if the response was queued, #GNUNET_SYSERR otherwise
   1185  */
   1186 static enum GNUNET_GenericReturnValue
   1187 phase_success_response (struct WithdrawContext *wc)
   1188 {
   1189   enum MHD_Result ret;
   1190 
   1191   GNUNET_assert (! wc->transaction_open);
   1192   ret = TALER_MHD_REPLY_JSON_PACK (
   1193     wc->connection,
   1194     MHD_HTTP_OK,
   1195     GNUNET_JSON_pack_array_steal ("grants",
   1196                                   wc->results));
   1197   wc->results = NULL;
   1198   return (MHD_YES == ret) ? GNUNET_NO : GNUNET_SYSERR;
   1199 }
   1200 
   1201 
   1202 enum MHD_Result
   1203 TMH_post_fountain_withdraw (const struct TMH_RequestHandler *rh,
   1204                             struct MHD_Connection *connection,
   1205                             struct TMH_HandlerContext *hc)
   1206 {
   1207   struct WithdrawContext *wc = hc->ctx;
   1208 
   1209   GNUNET_assert (NULL != hc->instance);
   1210   if (NULL == wc)
   1211   {
   1212     wc = GNUNET_new (struct WithdrawContext);
   1213     wc->connection = connection;
   1214     wc->hc = hc;
   1215     wc->now = GNUNET_TIME_timestamp_get ();
   1216     hc->ctx = wc;
   1217     hc->cc = &withdraw_context_cleanup;
   1218   }
   1219   while (1)
   1220   {
   1221     enum GNUNET_GenericReturnValue res;
   1222 
   1223     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1224                 "Processing /fountain/withdraw in phase %d\n",
   1225                 (int) wc->phase);
   1226     switch (wc->phase)
   1227     {
   1228     case WP_PARSE_REQUEST:
   1229       res = phase_parse_request (wc);
   1230       break;
   1231     case WP_AUTHENTICATE:
   1232       res = phase_authenticate (wc);
   1233       break;
   1234     case WP_START_TRANSACTION:
   1235       res = phase_start_transaction (wc);
   1236       break;
   1237     case WP_CHECK_REPLAY:
   1238       res = phase_check_replay (wc);
   1239       break;
   1240     case WP_LOAD_GRANTS:
   1241       res = phase_load_grants (wc);
   1242       break;
   1243     case WP_PARSE_ENTRIES:
   1244       res = phase_parse_entries (wc);
   1245       break;
   1246     case WP_RESOLVE_KEYS:
   1247       res = phase_resolve_keys (wc);
   1248       break;
   1249     case WP_CONSUME_QUOTA:
   1250       res = phase_consume_quota (wc);
   1251       break;
   1252     case WP_SIGN_TOKENS:
   1253       res = phase_sign_tokens (wc);
   1254       break;
   1255     case WP_COMMIT_TRANSACTION:
   1256       res = phase_commit_transaction (wc);
   1257       break;
   1258     case WP_SUCCESS_RESPONSE:
   1259       res = phase_success_response (wc);
   1260       break;
   1261     case WP_END_YES:
   1262       return MHD_YES;
   1263     case WP_END_NO:
   1264       return MHD_NO;
   1265     default:
   1266       GNUNET_assert (0);
   1267       return MHD_NO;
   1268     }
   1269     if (GNUNET_OK != res)
   1270     {
   1271       /* All phases run synchronously: release the transaction before
   1272          returning control to the HTTP server, including on queueing errors. */
   1273       if (wc->transaction_open)
   1274       {
   1275         TALER_MERCHANTDB_rollback (TMH_db);
   1276         wc->transaction_open = false;
   1277       }
   1278       wc->phase = (GNUNET_NO == res) ? WP_END_YES : WP_END_NO;
   1279     }
   1280   }
   1281 }
   1282 
   1283 
   1284 /* end of taler-merchant-httpd_post-fountain-withdraw.c */