LCOV - code coverage report
Current view: top level - exchange - taler-exchange-httpd_reserve_withdraw.c (source / functions) Hit Total Coverage
Test: rcoverage.info Lines: 101 165 61.2 %
Date: 2017-09-17 17:24:28 Functions: 4 4 100.0 %

          Line data    Source code
       1             : /*
       2             :   This file is part of TALER
       3             :   Copyright (C) 2014-2017 GNUnet e.V.
       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_reserve_withdraw.c
      18             :  * @brief Handle /reserve/withdraw requests
      19             :  * @author Florian Dold
      20             :  * @author Benedikt Mueller
      21             :  * @author Christian Grothoff
      22             :  */
      23             : #include "platform.h"
      24             : #include <gnunet/gnunet_util_lib.h>
      25             : #include <jansson.h>
      26             : #include "taler-exchange-httpd_reserve_withdraw.h"
      27             : #include "taler-exchange-httpd_parsing.h"
      28             : #include "taler-exchange-httpd_responses.h"
      29             : #include "taler-exchange-httpd_keystate.h"
      30             : 
      31             : 
      32             : /**
      33             :  * Send reserve status information to client with the
      34             :  * message that we have insufficient funds for the
      35             :  * requested /reserve/withdraw operation.
      36             :  *
      37             :  * @param connection connection to the client
      38             :  * @param rh reserve history to return
      39             :  * @return MHD result code
      40             :  */
      41             : static int
      42           1 : reply_reserve_withdraw_insufficient_funds (struct MHD_Connection *connection,
      43             :                                            const struct TALER_EXCHANGEDB_ReserveHistory *rh)
      44             : {
      45             :   json_t *json_balance;
      46             :   json_t *json_history;
      47             :   struct TALER_Amount balance;
      48             : 
      49           1 :   json_history = TEH_RESPONSE_compile_reserve_history (rh,
      50             :                                                        &balance);
      51           1 :   if (NULL == json_history)
      52           0 :     return TEH_RESPONSE_reply_internal_error (connection,
      53             :                                               TALER_EC_WITHDRAW_HISTORY_DB_ERROR_INSUFFICIENT_FUNDS,
      54             :                                               "balance calculation failure");
      55           1 :   json_balance = TALER_JSON_from_amount (&balance);
      56           1 :   return TEH_RESPONSE_reply_json_pack (connection,
      57             :                                        MHD_HTTP_FORBIDDEN,
      58             :                                        "{s:s, s:I, s:o, s:o}",
      59             :                                        "error", "Insufficient funds",
      60             :                                        "code", (json_int_t) TALER_EC_WITHDRAW_INSUFFICIENT_FUNDS,
      61             :                                        "balance", json_balance,
      62             :                                        "history", json_history);
      63             : }
      64             : 
      65             : 
      66             : /**
      67             :  * Send blinded coin information to client.
      68             :  *
      69             :  * @param connection connection to the client
      70             :  * @param collectable blinded coin to return
      71             :  * @return MHD result code
      72             :  */
      73             : static int
      74           6 : reply_reserve_withdraw_success (struct MHD_Connection *connection,
      75             :                                 const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable)
      76             : {
      77             :   json_t *sig_json;
      78             : 
      79           6 :   sig_json = GNUNET_JSON_from_rsa_signature (collectable->sig.rsa_signature);
      80           6 :   return TEH_RESPONSE_reply_json_pack (connection,
      81             :                                        MHD_HTTP_OK,
      82             :                                        "{s:o}",
      83             :                                        "ev_sig", sig_json);
      84             : }
      85             : 
      86             : 
      87             : /**
      88             :  * Context for #withdraw_transaction.
      89             :  */
      90             : struct WithdrawContext
      91             : {
      92             :   /**
      93             :    * Details about the withdrawal request.
      94             :    */
      95             :   struct TALER_WithdrawRequestPS wsrd;
      96             : 
      97             :   /**
      98             :    * Value of the coin plus withdraw fee.
      99             :    */
     100             :   struct TALER_Amount amount_required;
     101             : 
     102             :   /**
     103             :    * Denomination public key.
     104             :    */
     105             :   struct TALER_DenominationPublicKey denomination_pub;
     106             : 
     107             :   /**
     108             :    * Signature over the request.
     109             :    */
     110             :   struct TALER_ReserveSignatureP signature;
     111             : 
     112             :   /**
     113             :    * Blinded planchet.
     114             :    */
     115             :   char *blinded_msg;
     116             : 
     117             :   /**
     118             :    * Key state to use to inspect previous withdrawal values.
     119             :    */
     120             :   struct TEH_KS_StateHandle *key_state;
     121             : 
     122             :   /**
     123             :    * Number of bytes in @e blinded_msg.
     124             :    */
     125             :   size_t blinded_msg_len;
     126             : 
     127             :   /**
     128             :    * Details about denomination we are about to withdraw.
     129             :    */
     130             :   struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki;
     131             : 
     132             :   /**
     133             :    * Set to the resulting signed coin data to be returned to the client.
     134             :    */
     135             :   struct TALER_EXCHANGEDB_CollectableBlindcoin collectable;
     136             : 
     137             : };
     138             : 
     139             : 
     140             : /**
     141             :  * Function implementing /reserve/withdraw transaction.  Runs the
     142             :  * transaction logic; IF it returns a non-error code, the transaction
     143             :  * logic MUST NOT queue a MHD response.  IF it returns an hard error,
     144             :  * the transaction logic MUST queue a MHD response and set @a mhd_ret.
     145             :  * IF it returns the soft error code, the function MAY be called again
     146             :  * to retry and MUST not queue a MHD response.
     147             :  *
     148             :  * @param cls a `struct WithdrawContext *`
     149             :  * @param connection MHD request which triggered the transaction
     150             :  * @param session database session to use
     151             :  * @param[out] mhd_ret set to MHD response status for @a connection,
     152             :  *             if transaction failed (!)
     153             :  * @return transaction status
     154             :  */
     155             : static enum GNUNET_DB_QueryStatus
     156           7 : withdraw_transaction (void *cls,
     157             :                       struct MHD_Connection *connection,
     158             :                       struct TALER_EXCHANGEDB_Session *session,
     159             :                       int *mhd_ret)
     160             : {
     161           7 :   struct WithdrawContext *wc = cls;
     162             :   struct TALER_EXCHANGEDB_ReserveHistory *rh;
     163             :   struct TALER_Amount deposit_total;
     164             :   struct TALER_Amount withdraw_total;
     165             :   struct TALER_Amount balance;
     166             :   struct TALER_Amount value;
     167             :   struct TALER_Amount fee_withdraw;
     168             :   int res;
     169             :   enum GNUNET_DB_QueryStatus qs;
     170             :   struct TALER_DenominationSignature denom_sig;
     171             :   struct GNUNET_HashCode h_blind;
     172             : 
     173           7 :   GNUNET_CRYPTO_hash (wc->blinded_msg,
     174             :                       wc->blinded_msg_len,
     175             :                       &h_blind);
     176           7 :   qs = TEH_plugin->get_withdraw_info (TEH_plugin->cls,
     177             :                                       session,
     178             :                                       &h_blind,
     179             :                                       &wc->collectable);
     180           7 :   if (0 > qs)
     181             :   {
     182           0 :     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     183           0 :     if (GNUNET_DB_STATUS_HARD_ERROR == qs)
     184           0 :       *mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection,
     185             :                                                        TALER_EC_WITHDRAW_DB_FETCH_ERROR);
     186           0 :     return qs;
     187             :   }
     188             : 
     189             :   /* Don't sign again if we have already signed the coin */
     190           7 :   if (1 == qs)
     191           0 :     return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
     192           7 :   GNUNET_assert (0 == qs);
     193             : 
     194             :   /* Check if balance is sufficient */
     195          14 :   qs = TEH_plugin->get_reserve_history (TEH_plugin->cls,
     196             :                                         session,
     197           7 :                                         &wc->wsrd.reserve_pub,
     198             :                                         &rh);
     199           7 :   if (0 > qs)
     200             :   {
     201           0 :     if (GNUNET_DB_STATUS_HARD_ERROR == qs)
     202           0 :       *mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection,
     203             :                                                        TALER_EC_WITHDRAW_DB_FETCH_ERROR);
     204           0 :     return qs;
     205             :   }
     206           7 :   if (NULL == rh)
     207             :   {
     208           0 :     *mhd_ret = TEH_RESPONSE_reply_arg_unknown (connection,
     209             :                                                TALER_EC_WITHDRAW_RESERVE_UNKNOWN,
     210             :                                                "reserve_pub");
     211           0 :     return GNUNET_DB_STATUS_HARD_ERROR;
     212             :   }
     213             : 
     214             :   /* calculate balance of the reserve */
     215           7 :   res = 0;
     216          23 :   for (const struct TALER_EXCHANGEDB_ReserveHistory *pos = rh;
     217             :        NULL != pos;
     218           9 :        pos = pos->next)
     219             :   {
     220           9 :     switch (pos->type)
     221             :     {
     222             :     case TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE:
     223           7 :       if (0 == (res & 1))
     224           7 :         deposit_total = pos->details.bank->amount;
     225             :       else
     226           0 :         if (GNUNET_OK !=
     227           0 :             TALER_amount_add (&deposit_total,
     228             :                               &deposit_total,
     229           0 :                               &pos->details.bank->amount))
     230             :         {
     231           0 :           *mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection,
     232             :                                                            TALER_EC_WITHDRAW_AMOUNT_DEPOSITS_OVERFLOW);
     233           0 :           return GNUNET_DB_STATUS_HARD_ERROR;
     234             :         }
     235           7 :       res |= 1;
     236           7 :       break;
     237             :     case TALER_EXCHANGEDB_RO_WITHDRAW_COIN:
     238             :       {
     239             :         struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *tdki;
     240             : 
     241           2 :         tdki = TEH_KS_denomination_key_lookup (wc->key_state,
     242           2 :                                                &pos->details.withdraw->denom_pub,
     243             :                                                TEH_KS_DKU_WITHDRAW);
     244           2 :         if (NULL == tdki)
     245             :         {
     246           0 :           GNUNET_break (0);
     247           0 :           *mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection,
     248             :                                                            TALER_EC_WITHDRAW_HISTORIC_DENOMINATION_KEY_NOT_FOUND);
     249           0 :           return GNUNET_DB_STATUS_HARD_ERROR;
     250             :         }
     251           2 :         TALER_amount_ntoh (&value,
     252           2 :                            &tdki->issue.properties.value);
     253           2 :         if (0 == (res & 2))
     254           2 :           withdraw_total = value;
     255             :         else
     256           0 :           if (GNUNET_OK !=
     257           0 :               TALER_amount_add (&withdraw_total,
     258             :                                 &withdraw_total,
     259             :                                 &value))
     260             :           {
     261           0 :             *mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection,
     262             :                                                              TALER_EC_WITHDRAW_AMOUNT_WITHDRAWALS_OVERFLOW);
     263           0 :             return GNUNET_DB_STATUS_HARD_ERROR;
     264             :           }
     265           2 :         res |= 2;
     266           2 :         break;
     267             :       }
     268             :     case TALER_EXCHANGEDB_RO_PAYBACK_COIN:
     269           0 :       if (0 == (res & 1))
     270           0 :         deposit_total = pos->details.payback->value;
     271             :       else
     272           0 :         if (GNUNET_OK !=
     273           0 :             TALER_amount_add (&deposit_total,
     274             :                               &deposit_total,
     275           0 :                               &pos->details.payback->value))
     276             :         {
     277           0 :           *mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection,
     278             :                                                            TALER_EC_WITHDRAW_AMOUNT_DEPOSITS_OVERFLOW);
     279           0 :           return GNUNET_DB_STATUS_HARD_ERROR;
     280             :         }
     281           0 :       res |= 1;
     282           0 :       break;
     283             : 
     284             :     case TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK:
     285           0 :       if (0 == (res & 2))
     286           0 :         withdraw_total = pos->details.bank->amount;
     287             :       else
     288           0 :         if (GNUNET_OK !=
     289           0 :             TALER_amount_add (&withdraw_total,
     290             :                               &withdraw_total,
     291           0 :                               &pos->details.bank->amount))
     292             :         {
     293           0 :           *mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection,
     294             :                                                            TALER_EC_WITHDRAW_AMOUNT_WITHDRAWALS_OVERFLOW);
     295           0 :           return GNUNET_DB_STATUS_HARD_ERROR;
     296             :         }
     297           0 :       res |= 2;
     298           0 :       break;
     299             :     }
     300             :   }
     301           7 :   if (0 == (res & 1))
     302             :   {
     303             :     /* did not encounter any wire transfer operations, how can we have a reserve? */
     304           0 :     GNUNET_break (0);
     305           0 :     *mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection,
     306             :                                                      TALER_EC_WITHDRAW_RESERVE_WITHOUT_WIRE_TRANSFER);
     307           0 :     return GNUNET_DB_STATUS_HARD_ERROR;
     308             :   }
     309           7 :   if (0 == (res & 2))
     310             :   {
     311             :     /* did not encounter any withdraw operations, set to zero */
     312           5 :     TALER_amount_get_zero (deposit_total.currency,
     313             :                            &withdraw_total);
     314             :   }
     315             :   /* All reserve balances should be non-negative */
     316           7 :   if (GNUNET_SYSERR ==
     317           7 :       TALER_amount_subtract (&balance,
     318             :                              &deposit_total,
     319             :                              &withdraw_total))
     320             :   {
     321           0 :     GNUNET_break (0); /* database inconsistent */
     322           0 :     *mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection,
     323             :                                                      TALER_EC_WITHDRAW_RESERVE_HISTORY_IMPOSSIBLE);
     324           0 :     return GNUNET_DB_STATUS_HARD_ERROR;
     325             :   }
     326           7 :   if (0 < TALER_amount_cmp (&wc->amount_required,
     327             :                             &balance))
     328             :   {
     329           1 :     *mhd_ret = reply_reserve_withdraw_insufficient_funds (connection,
     330             :                                                           rh);
     331           1 :     TEH_plugin->free_reserve_history (TEH_plugin->cls,
     332             :                                       rh);
     333           1 :     return GNUNET_DB_STATUS_HARD_ERROR;
     334             :   }
     335           6 :   TEH_plugin->free_reserve_history (TEH_plugin->cls,
     336             :                                     rh);
     337             : 
     338             :   /* Balance is good, sign the coin! */
     339             :   denom_sig.rsa_signature
     340          12 :     = GNUNET_CRYPTO_rsa_sign_blinded (wc->dki->denom_priv.rsa_private_key,
     341           6 :                                       wc->blinded_msg,
     342             :                                       wc->blinded_msg_len);
     343           6 :   if (NULL == denom_sig.rsa_signature)
     344             :   {
     345           0 :     GNUNET_break (0);
     346           0 :     *mhd_ret = TEH_RESPONSE_reply_internal_error (connection,
     347             :                                                   TALER_EC_WITHDRAW_SIGNATURE_FAILED,
     348             :                                                   "Internal error");
     349           0 :     return GNUNET_DB_STATUS_HARD_ERROR;
     350             :   }
     351           6 :   TALER_amount_ntoh (&fee_withdraw,
     352           6 :                      &wc->dki->issue.properties.fee_withdraw);
     353           6 :   wc->collectable.sig = denom_sig;
     354           6 :   wc->collectable.denom_pub = wc->denomination_pub;
     355           6 :   wc->collectable.amount_with_fee = wc->amount_required;
     356           6 :   wc->collectable.withdraw_fee = fee_withdraw;
     357           6 :   wc->collectable.reserve_pub = wc->wsrd.reserve_pub;
     358           6 :   wc->collectable.h_coin_envelope = h_blind;
     359           6 :   wc->collectable.reserve_sig = wc->signature;
     360          12 :   qs = TEH_plugin->insert_withdraw_info (TEH_plugin->cls,
     361             :                                          session,
     362           6 :                                          &wc->collectable);
     363           6 :   if (0 > qs)
     364             :   {
     365           0 :     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     366           0 :     GNUNET_CRYPTO_rsa_signature_free (denom_sig.rsa_signature);
     367           0 :     if (GNUNET_DB_STATUS_HARD_ERROR == qs)
     368           0 :       *mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection,
     369             :                                                        TALER_EC_WITHDRAW_DB_STORE_ERROR);
     370           0 :     return qs;
     371             :   }
     372           6 :   return qs;
     373             : }
     374             : 
     375             : 
     376             : /**
     377             :  * Handle a "/reserve/withdraw" request.  Parses the "reserve_pub"
     378             :  * EdDSA key of the reserve and the requested "denom_pub" which
     379             :  * specifies the key/value of the coin to be withdrawn, and checks
     380             :  * that the signature "reserve_sig" makes this a valid withdrawal
     381             :  * request from the specified reserve.  If so, the envelope
     382             :  * with the blinded coin "coin_ev" is passed down to execute the
     383             :  * withdrawl operation.
     384             :  *
     385             :  * @param rh context of the handler
     386             :  * @param connection the MHD connection to handle
     387             :  * @param[in,out] connection_cls the connection's closure (can be updated)
     388             :  * @param upload_data upload data
     389             :  * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
     390             :  * @return MHD result code
     391             :  */
     392             : int
     393          24 : TEH_RESERVE_handler_reserve_withdraw (struct TEH_RequestHandler *rh,
     394             :                                       struct MHD_Connection *connection,
     395             :                                       void **connection_cls,
     396             :                                       const char *upload_data,
     397             :                                       size_t *upload_data_size)
     398             : {
     399             :   struct WithdrawContext wc;
     400             :   json_t *root;
     401             :   int res;
     402             :   int mhd_ret;
     403             :   struct TALER_Amount amount;
     404             :   struct TALER_Amount fee_withdraw;
     405          24 :   struct GNUNET_JSON_Specification spec[] = {
     406             :     GNUNET_JSON_spec_varsize ("coin_ev",
     407             :                               (void **) &wc.blinded_msg,
     408             :                               &wc.blinded_msg_len),
     409             :     GNUNET_JSON_spec_fixed_auto ("reserve_pub",
     410             :                                  &wc.wsrd.reserve_pub),
     411             :     GNUNET_JSON_spec_fixed_auto ("reserve_sig",
     412             :                                  &wc.signature),
     413             :     TALER_JSON_spec_denomination_public_key ("denom_pub",
     414             :                                              &wc.denomination_pub),
     415             :     GNUNET_JSON_spec_end ()
     416             :   };
     417             : 
     418          24 :   res = TEH_PARSE_post_json (connection,
     419             :                              connection_cls,
     420             :                              upload_data,
     421             :                              upload_data_size,
     422             :                              &root);
     423          24 :   if (GNUNET_SYSERR == res)
     424           0 :     return MHD_NO;
     425          24 :   if ( (GNUNET_NO == res) || (NULL == root) )
     426          16 :     return MHD_YES;
     427           8 :   res = TEH_PARSE_json_data (connection,
     428             :                              root,
     429             :                              spec);
     430           8 :   json_decref (root);
     431           8 :   if (GNUNET_OK != res)
     432           0 :     return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
     433           8 :   wc.key_state = TEH_KS_acquire ();
     434           8 :   wc.dki = TEH_KS_denomination_key_lookup (wc.key_state,
     435             :                                            &wc.denomination_pub,
     436             :                                            TEH_KS_DKU_WITHDRAW);
     437           8 :   if (NULL == wc.dki)
     438             :   {
     439           1 :     GNUNET_JSON_parse_free (spec);
     440           1 :     TEH_KS_release (wc.key_state);
     441           1 :     return TEH_RESPONSE_reply_arg_unknown (connection,
     442             :                                            TALER_EC_WITHDRAW_DENOMINATION_KEY_NOT_FOUND,
     443             :                                            "denom_pub");
     444             :   }
     445           7 :   TALER_amount_ntoh (&amount,
     446           7 :                      &wc.dki->issue.properties.value);
     447           7 :   TALER_amount_ntoh (&fee_withdraw,
     448           7 :                      &wc.dki->issue.properties.fee_withdraw);
     449           7 :   if (GNUNET_OK !=
     450           7 :       TALER_amount_add (&wc.amount_required,
     451             :                         &amount,
     452             :                         &fee_withdraw))
     453             :   {
     454           0 :     GNUNET_JSON_parse_free (spec);
     455           0 :     TEH_KS_release (wc.key_state);
     456           0 :     return TEH_RESPONSE_reply_internal_error (connection,
     457             :                                               TALER_EC_WITHDRAW_AMOUNT_FEE_OVERFLOW,
     458             :                                               "amount overflow for value plus withdraw fee");
     459             :   }
     460           7 :   TALER_amount_hton (&wc.wsrd.amount_with_fee,
     461             :                      &wc.amount_required);
     462           7 :   TALER_amount_hton (&wc.wsrd.withdraw_fee,
     463             :                      &fee_withdraw);
     464             :   /* verify signature! */
     465             :   wc.wsrd.purpose.size
     466           7 :     = htonl (sizeof (struct TALER_WithdrawRequestPS));
     467             :   wc.wsrd.purpose.purpose
     468           7 :     = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW);
     469           7 :   GNUNET_CRYPTO_rsa_public_key_hash (wc.denomination_pub.rsa_public_key,
     470             :                                      &wc.wsrd.h_denomination_pub);
     471           7 :   GNUNET_CRYPTO_hash (wc.blinded_msg,
     472             :                       wc.blinded_msg_len,
     473             :                       &wc.wsrd.h_coin_envelope);
     474           7 :   if (GNUNET_OK !=
     475           7 :       GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW,
     476             :                                   &wc.wsrd.purpose,
     477             :                                   &wc.signature.eddsa_signature,
     478             :                                   &wc.wsrd.reserve_pub.eddsa_pub))
     479             :   {
     480           0 :     TALER_LOG_WARNING ("Client supplied invalid signature for /reserve/withdraw request\n");
     481           0 :     GNUNET_JSON_parse_free (spec);
     482           0 :     TEH_KS_release (wc.key_state);
     483           0 :     return TEH_RESPONSE_reply_signature_invalid (connection,
     484             :                                                  TALER_EC_WITHDRAW_RESERVE_SIGNATURE_INVALID,
     485             :                                                  "reserve_sig");
     486             :   }
     487             : 
     488           7 :   if (GNUNET_OK !=
     489           7 :       TEH_DB_run_transaction (connection,
     490             :                               &mhd_ret,
     491             :                               &withdraw_transaction,
     492             :                               &wc))
     493             :   {
     494           1 :     TEH_KS_release (wc.key_state);
     495           1 :     GNUNET_JSON_parse_free (spec);
     496           1 :     return mhd_ret;
     497             :   }
     498           6 :   TEH_KS_release (wc.key_state);
     499           6 :   GNUNET_JSON_parse_free (spec);
     500             : 
     501           6 :   mhd_ret = reply_reserve_withdraw_success (connection,
     502             :                                             &wc.collectable);
     503           6 :   GNUNET_CRYPTO_rsa_signature_free (wc.collectable.sig.rsa_signature);
     504           6 :   return mhd_ret;
     505             : }
     506             : 
     507             : 
     508             : /* end of taler-exchange-httpd_reserve_withdraw.c */

Generated by: LCOV version 1.13