exchange

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

exchange_api_post-batch-deposit.c (26579B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2014-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 General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     12 
     13   You should have received a copy of the GNU General Public License along with
     14   TALER; see the file COPYING.  If not, see
     15   <http://www.gnu.org/licenses/>
     16 */
     17 /**
     18  * @file lib/exchange_api_post-batch-deposit.c
     19  * @brief Implementation of the /batch-deposit request of the exchange's HTTP API
     20  * @author Sree Harsha Totakura <sreeharsha@totakura.in>
     21  * @author Christian Grothoff
     22  */
     23 #include "taler/platform.h"
     24 #include <jansson.h>
     25 #include <microhttpd.h> /* just for HTTP status codes */
     26 #include <gnunet/gnunet_util_lib.h>
     27 #include <gnunet/gnunet_json_lib.h>
     28 #include <gnunet/gnunet_curl_lib.h>
     29 #include "taler/taler_json_lib.h"
     30 #include "taler/taler_auditor_service.h"
     31 #include "taler/taler_exchange_service.h"
     32 #include "exchange_api_common.h"
     33 #include "exchange_api_handle.h"
     34 #include "taler/taler_signatures.h"
     35 #include "exchange_api_curl_defaults.h"
     36 
     37 
     38 /**
     39  * 1:#AUDITOR_CHANCE is the probability that we report deposits
     40  * to the auditor.
     41  *
     42  * 20==5% of going to auditor. This is possibly still too high, but set
     43  * deliberately this high for testing
     44  */
     45 #define AUDITOR_CHANCE 20
     46 
     47 
     48 /**
     49  * Entry in list of ongoing interactions with an auditor.
     50  */
     51 struct TEAH_AuditorInteractionEntry
     52 {
     53   /**
     54    * DLL entry.
     55    */
     56   struct TEAH_AuditorInteractionEntry *next;
     57 
     58   /**
     59    * DLL entry.
     60    */
     61   struct TEAH_AuditorInteractionEntry *prev;
     62 
     63   /**
     64    * URL of our auditor. For logging.
     65    */
     66   const char *auditor_url;
     67 
     68   /**
     69    * Interaction state.
     70    */
     71   struct TALER_AUDITOR_DepositConfirmationHandle *dch;
     72 
     73   /**
     74    * Batch deposit this is for.
     75    */
     76   struct TALER_EXCHANGE_PostBatchDepositHandle *dh;
     77 };
     78 
     79 
     80 /**
     81  * @brief A Batch Deposit Handle
     82  */
     83 struct TALER_EXCHANGE_PostBatchDepositHandle
     84 {
     85 
     86   /**
     87    * The keys of the exchange.
     88    */
     89   struct TALER_EXCHANGE_Keys *keys;
     90 
     91   /**
     92    * Context for our curl request(s).
     93    */
     94   struct GNUNET_CURL_Context *ctx;
     95 
     96   /**
     97    * The base URL of the exchange (used to build the endpoint URL in _start).
     98    */
     99   char *base_url;
    100 
    101   /**
    102    * The full endpoint URL for this request (built in _start).
    103    */
    104   char *url;
    105 
    106   /**
    107    * Context for #TEH_curl_easy_post(). Keeps the data that must
    108    * persist for Curl to make the upload.
    109    */
    110   struct TALER_CURL_PostContext post_ctx;
    111 
    112   /**
    113    * Handle for the request.
    114    */
    115   struct GNUNET_CURL_Job *job;
    116 
    117   /**
    118    * Function to call with the result.
    119    */
    120   TALER_EXCHANGE_PostBatchDepositCallback cb;
    121 
    122   /**
    123    * Closure for @a cb.
    124    */
    125   void *cb_cls;
    126 
    127   /**
    128    * Details about the contract.
    129    */
    130   struct TALER_EXCHANGE_DepositContractDetail dcd;
    131 
    132   /**
    133    * Array with details about the coins.
    134    */
    135   struct TALER_EXCHANGE_CoinDepositDetail *cdds;
    136 
    137   /**
    138    * Hash of the merchant's wire details.
    139    */
    140   struct TALER_MerchantWireHashP h_wire;
    141 
    142   /**
    143    * Hash over the extensions, or all zero.
    144    */
    145   struct TALER_ExtensionPolicyHashP h_policy;
    146 
    147   /**
    148    * Time when this confirmation was generated / when the exchange received
    149    * the deposit request.
    150    */
    151   struct GNUNET_TIME_Timestamp exchange_timestamp;
    152 
    153   /**
    154    * Exchange signature, set for #auditor_cb.
    155    */
    156   struct TALER_ExchangeSignatureP exchange_sig;
    157 
    158   /**
    159    * Head of DLL of interactions with this auditor.
    160    */
    161   struct TEAH_AuditorInteractionEntry *ai_head;
    162 
    163   /**
    164    * Tail of DLL of interactions with this auditor.
    165    */
    166   struct TEAH_AuditorInteractionEntry *ai_tail;
    167 
    168   /**
    169    * Result to return to the application once @e ai_head is empty.
    170    */
    171   struct TALER_EXCHANGE_PostBatchDepositResponse dr;
    172 
    173   /**
    174    * Exchange signing public key, set for #auditor_cb.
    175    */
    176   struct TALER_ExchangePublicKeyP exchange_pub;
    177 
    178   /**
    179    * Total amount deposited without fees as calculated by us.
    180    */
    181   struct TALER_Amount total_without_fee;
    182 
    183   /**
    184    * Response object to free at the end.
    185    */
    186   json_t *response;
    187 
    188   /**
    189    * The JSON body to POST (built during _create, used during _start).
    190    */
    191   json_t *deposit_obj;
    192 
    193   /**
    194    * Chance that we will inform the auditor about the deposit
    195    * is 1:n, where the value of this field is "n".
    196    */
    197   unsigned int auditor_chance;
    198 
    199   /**
    200    * Length of the @e cdds array.
    201    */
    202   unsigned int num_cdds;
    203 
    204 };
    205 
    206 
    207 /**
    208  * Finish batch deposit operation by calling the callback.
    209  *
    210  * @param[in] dh handle to finished batch deposit operation
    211  */
    212 static void
    213 finish_dh (struct TALER_EXCHANGE_PostBatchDepositHandle *dh)
    214 {
    215   dh->cb (dh->cb_cls,
    216           &dh->dr);
    217   TALER_EXCHANGE_post_batch_deposit_cancel (dh);
    218 }
    219 
    220 
    221 /**
    222  * Function called with the result from our call to the
    223  * auditor's /deposit-confirmation handler.
    224  *
    225  * @param cls closure of type `struct TEAH_AuditorInteractionEntry *`
    226  * @param dcr response
    227  */
    228 static void
    229 acc_confirmation_cb (
    230   void *cls,
    231   const struct TALER_AUDITOR_DepositConfirmationResponse *dcr)
    232 {
    233   struct TEAH_AuditorInteractionEntry *aie = cls;
    234   struct TALER_EXCHANGE_PostBatchDepositHandle *dh = aie->dh;
    235 
    236   if (MHD_HTTP_OK != dcr->hr.http_status)
    237   {
    238     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    239                 "Failed to submit deposit confirmation to auditor `%s' with HTTP status %d (EC: %d). This is acceptable if it does not happen often.\n",
    240                 aie->auditor_url,
    241                 dcr->hr.http_status,
    242                 dcr->hr.ec);
    243   }
    244   GNUNET_CONTAINER_DLL_remove (dh->ai_head,
    245                                dh->ai_tail,
    246                                aie);
    247   GNUNET_free (aie);
    248   if (NULL == dh->ai_head)
    249     finish_dh (dh);
    250 }
    251 
    252 
    253 /**
    254  * Function called for each auditor to give us a chance to possibly
    255  * launch a deposit confirmation interaction.
    256  *
    257  * @param cls closure
    258  * @param auditor_url base URL of the auditor
    259  * @param auditor_pub public key of the auditor
    260  */
    261 static void
    262 auditor_cb (void *cls,
    263             const char *auditor_url,
    264             const struct TALER_AuditorPublicKeyP *auditor_pub)
    265 {
    266   struct TALER_EXCHANGE_PostBatchDepositHandle *dh = cls;
    267   const struct TALER_EXCHANGE_SigningPublicKey *spk;
    268   struct TEAH_AuditorInteractionEntry *aie;
    269   const struct TALER_CoinSpendSignatureP *csigs[GNUNET_NZL (
    270                                                   dh->num_cdds)];
    271   const struct TALER_CoinSpendPublicKeyP *cpubs[GNUNET_NZL (
    272                                                   dh->num_cdds)];
    273 
    274   for (unsigned int i = 0; i<dh->num_cdds; i++)
    275   {
    276     const struct TALER_EXCHANGE_CoinDepositDetail *cdd = &dh->cdds[i];
    277 
    278     csigs[i] = &cdd->coin_sig;
    279     cpubs[i] = &cdd->coin_pub;
    280   }
    281 
    282   if (0 !=
    283       GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK,
    284                                 dh->auditor_chance))
    285   {
    286     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    287                 "Not providing deposit confirmation to auditor\n");
    288     return;
    289   }
    290   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    291               "Will provide deposit confirmation to auditor `%s'\n",
    292               TALER_B2S (auditor_pub));
    293   spk = TALER_EXCHANGE_get_signing_key_info (dh->keys,
    294                                              &dh->exchange_pub);
    295   if (NULL == spk)
    296   {
    297     GNUNET_break_op (0);
    298     return;
    299   }
    300   aie = GNUNET_new (struct TEAH_AuditorInteractionEntry);
    301   aie->dh = dh;
    302   aie->auditor_url = auditor_url;
    303   aie->dch = TALER_AUDITOR_deposit_confirmation (
    304     dh->ctx,
    305     auditor_url,
    306     &dh->h_wire,
    307     &dh->h_policy,
    308     &dh->dcd.h_contract_terms,
    309     dh->exchange_timestamp,
    310     dh->dcd.wire_deadline,
    311     dh->dcd.refund_deadline,
    312     &dh->total_without_fee,
    313     dh->num_cdds,
    314     cpubs,
    315     csigs,
    316     &dh->dcd.merchant_pub,
    317     &dh->exchange_pub,
    318     &dh->exchange_sig,
    319     &dh->keys->master_pub,
    320     spk->valid_from,
    321     spk->valid_until,
    322     spk->valid_legal,
    323     &spk->master_sig,
    324     &acc_confirmation_cb,
    325     aie);
    326   if (NULL == aie->dch)
    327   {
    328     GNUNET_break (0);
    329     GNUNET_free (aie);
    330     return;
    331   }
    332   GNUNET_CONTAINER_DLL_insert (dh->ai_head,
    333                                dh->ai_tail,
    334                                aie);
    335 }
    336 
    337 
    338 /**
    339  * Function called when we're done processing the
    340  * HTTP /batch-deposit request.
    341  *
    342  * @param cls the `struct TALER_EXCHANGE_PostBatchDepositHandle`
    343  * @param response_code HTTP response code, 0 on error
    344  * @param response parsed JSON result, NULL on error
    345  */
    346 static void
    347 handle_deposit_finished (void *cls,
    348                          long response_code,
    349                          const void *response)
    350 {
    351   struct TALER_EXCHANGE_PostBatchDepositHandle *dh = cls;
    352   const json_t *j = response;
    353   struct TALER_EXCHANGE_PostBatchDepositResponse *dr = &dh->dr;
    354 
    355   dh->job = NULL;
    356   dh->response = json_incref ((json_t*) j);
    357   dr->hr.reply = dh->response;
    358   dr->hr.http_status = (unsigned int) response_code;
    359   switch (response_code)
    360   {
    361   case 0:
    362     dr->hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    363     break;
    364   case MHD_HTTP_OK:
    365     {
    366       bool prev33;
    367       struct GNUNET_JSON_Specification spec[] = {
    368         GNUNET_JSON_spec_fixed_auto ("exchange_sig",
    369                                      &dh->exchange_sig),
    370         GNUNET_JSON_spec_fixed_auto ("exchange_pub",
    371                                      &dh->exchange_pub),
    372         GNUNET_JSON_spec_mark_optional (
    373           TALER_JSON_spec_amount (
    374             "accumulated_total_without_fee",
    375             dh->total_without_fee.currency,
    376             &dr->details.ok.accumulated_total_without_fee),
    377           &prev33),
    378         GNUNET_JSON_spec_mark_optional (
    379           TALER_JSON_spec_web_url ("transaction_base_url",
    380                                    &dr->details.ok.transaction_base_url),
    381           NULL),
    382         GNUNET_JSON_spec_timestamp ("exchange_timestamp",
    383                                     &dh->exchange_timestamp),
    384         GNUNET_JSON_spec_end ()
    385       };
    386 
    387       if (GNUNET_OK !=
    388           GNUNET_JSON_parse (j,
    389                              spec,
    390                              NULL, NULL))
    391       {
    392         GNUNET_break_op (0);
    393         dr->hr.http_status = 0;
    394         dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    395         break;
    396       }
    397       if (GNUNET_OK !=
    398           TALER_EXCHANGE_test_signing_key (dh->keys,
    399                                            &dh->exchange_pub))
    400       {
    401         GNUNET_break_op (0);
    402         dr->hr.http_status = 0;
    403         dr->hr.ec = TALER_EC_EXCHANGE_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE;
    404         break;
    405       }
    406       if (prev33)
    407         dr->details.ok.accumulated_total_without_fee
    408           = dh->total_without_fee;
    409       if (1 ==
    410           TALER_amount_cmp (&dh->total_without_fee,
    411                             &dr->details.ok.accumulated_total_without_fee))
    412       {
    413         /* Amount signed by exchange is SMALLER than what we deposited */
    414         GNUNET_break_op (0);
    415         dr->hr.http_status = 0;
    416         dr->hr.ec = TALER_EC_EXCHANGE_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE;
    417         break;
    418       }
    419       {
    420         const struct TALER_CoinSpendSignatureP *csigs[
    421           GNUNET_NZL (dh->num_cdds)];
    422 
    423         for (unsigned int i = 0; i<dh->num_cdds; i++)
    424           csigs[i] = &dh->cdds[i].coin_sig;
    425         if (GNUNET_OK !=
    426             TALER_exchange_online_deposit_confirmation_verify (
    427               &dh->dcd.h_contract_terms,
    428               &dh->h_wire,
    429               &dh->h_policy,
    430               dh->exchange_timestamp,
    431               dh->dcd.wire_deadline,
    432               dh->dcd.refund_deadline,
    433               &dr->details.ok.accumulated_total_without_fee,
    434               dh->num_cdds,
    435               csigs,
    436               &dh->dcd.merchant_pub,
    437               &dh->exchange_pub,
    438               &dh->exchange_sig))
    439         {
    440           GNUNET_break_op (0);
    441           dr->hr.http_status = 0;
    442           dr->hr.ec = TALER_EC_EXCHANGE_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE;
    443           break;
    444         }
    445       }
    446       dh->total_without_fee = dr->details.ok.accumulated_total_without_fee;
    447       TALER_EXCHANGE_get_auditors_for_dc_ (dh->keys,
    448                                            &auditor_cb,
    449                                            dh);
    450     }
    451     dr->details.ok.exchange_sig = &dh->exchange_sig;
    452     dr->details.ok.exchange_pub = &dh->exchange_pub;
    453     dr->details.ok.deposit_timestamp = dh->exchange_timestamp;
    454     break;
    455   case MHD_HTTP_BAD_REQUEST:
    456     /* This should never happen, either us or the exchange is buggy
    457        (or API version conflict); just pass JSON reply to the application */
    458     dr->hr.ec = TALER_JSON_get_error_code (j);
    459     dr->hr.hint = TALER_JSON_get_error_hint (j);
    460     break;
    461   case MHD_HTTP_FORBIDDEN:
    462     dr->hr.ec = TALER_JSON_get_error_code (j);
    463     dr->hr.hint = TALER_JSON_get_error_hint (j);
    464     /* Nothing really to verify, exchange says one of the signatures is
    465        invalid; as we checked them, this should never happen, we
    466        should pass the JSON reply to the application */
    467     break;
    468   case MHD_HTTP_NOT_FOUND:
    469     dr->hr.ec = TALER_JSON_get_error_code (j);
    470     dr->hr.hint = TALER_JSON_get_error_hint (j);
    471     /* Nothing really to verify, this should never
    472        happen, we should pass the JSON reply to the application */
    473     break;
    474   case MHD_HTTP_CONFLICT:
    475     {
    476       dr->hr.ec = TALER_JSON_get_error_code (j);
    477       dr->hr.hint = TALER_JSON_get_error_hint (j);
    478       switch (dr->hr.ec)
    479       {
    480       case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS:
    481         {
    482           struct GNUNET_JSON_Specification spec[] = {
    483             GNUNET_JSON_spec_fixed_auto (
    484               "coin_pub",
    485               &dr->details.conflict.details
    486               .insufficient_funds.coin_pub),
    487             GNUNET_JSON_spec_end ()
    488           };
    489 
    490           if (GNUNET_OK !=
    491               GNUNET_JSON_parse (j,
    492                                  spec,
    493                                  NULL, NULL))
    494           {
    495             GNUNET_break_op (0);
    496             dr->hr.http_status = 0;
    497             dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    498             break;
    499           }
    500         }
    501         break;
    502       case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_AGE_HASH:
    503         {
    504           struct GNUNET_JSON_Specification spec[] = {
    505             GNUNET_JSON_spec_fixed_auto (
    506               "coin_pub",
    507               &dr->details.conflict.details
    508               .coin_conflicting_age_hash.coin_pub),
    509             GNUNET_JSON_spec_end ()
    510           };
    511 
    512           if (GNUNET_OK !=
    513               GNUNET_JSON_parse (j,
    514                                  spec,
    515                                  NULL, NULL))
    516           {
    517             GNUNET_break_op (0);
    518             dr->hr.http_status = 0;
    519             dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    520             break;
    521           }
    522         }
    523         break;
    524       case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY:
    525         {
    526           struct GNUNET_JSON_Specification spec[] = {
    527             GNUNET_JSON_spec_fixed_auto (
    528               "coin_pub",
    529               &dr->details.conflict.details
    530               .coin_conflicting_denomination_key.coin_pub),
    531             GNUNET_JSON_spec_end ()
    532           };
    533 
    534           if (GNUNET_OK !=
    535               GNUNET_JSON_parse (j,
    536                                  spec,
    537                                  NULL, NULL))
    538           {
    539             GNUNET_break_op (0);
    540             dr->hr.http_status = 0;
    541             dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    542             break;
    543           }
    544         }
    545         break;
    546       case TALER_EC_EXCHANGE_DEPOSIT_CONFLICTING_CONTRACT:
    547         break;
    548       default:
    549         GNUNET_break_op (0);
    550         break;
    551       }
    552     }
    553     break;
    554   case MHD_HTTP_GONE:
    555     /* could happen if denomination was revoked */
    556     /* Note: one might want to check /keys for revocation
    557        signature here, alas tricky in case our /keys
    558        is outdated => left to clients */
    559     dr->hr.ec = TALER_JSON_get_error_code (j);
    560     dr->hr.hint = TALER_JSON_get_error_hint (j);
    561     break;
    562   case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
    563     {
    564       struct GNUNET_JSON_Specification spec[] = {
    565         GNUNET_JSON_spec_fixed_auto (
    566           "h_payto",
    567           &dr->details.unavailable_for_legal_reasons.h_payto),
    568         GNUNET_JSON_spec_uint64 (
    569           "requirement_row",
    570           &dr->details.unavailable_for_legal_reasons.requirement_row),
    571         GNUNET_JSON_spec_bool (
    572           "bad_kyc_auth",
    573           &dr->details.unavailable_for_legal_reasons.bad_kyc_auth),
    574         GNUNET_JSON_spec_end ()
    575       };
    576 
    577       if (GNUNET_OK !=
    578           GNUNET_JSON_parse (j,
    579                              spec,
    580                              NULL, NULL))
    581       {
    582         GNUNET_break_op (0);
    583         dr->hr.http_status = 0;
    584         dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    585         break;
    586       }
    587     }
    588     break;
    589   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    590     dr->hr.ec = TALER_JSON_get_error_code (j);
    591     dr->hr.hint = TALER_JSON_get_error_hint (j);
    592     /* Server had an internal issue; we should retry, but this API
    593        leaves this to the application */
    594     break;
    595   default:
    596     /* unexpected response code */
    597     dr->hr.ec = TALER_JSON_get_error_code (j);
    598     dr->hr.hint = TALER_JSON_get_error_hint (j);
    599     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    600                 "Unexpected response code %u/%d for exchange deposit\n",
    601                 (unsigned int) response_code,
    602                 dr->hr.ec);
    603     GNUNET_break_op (0);
    604     break;
    605   }
    606   if (NULL != dh->ai_head)
    607     return;
    608   finish_dh (dh);
    609 }
    610 
    611 
    612 struct TALER_EXCHANGE_PostBatchDepositHandle *
    613 TALER_EXCHANGE_post_batch_deposit_create (
    614   struct GNUNET_CURL_Context *ctx,
    615   const char *url,
    616   struct TALER_EXCHANGE_Keys *keys,
    617   const struct TALER_EXCHANGE_DepositContractDetail *dcd,
    618   unsigned int num_cdds,
    619   const struct TALER_EXCHANGE_CoinDepositDetail cdds[static num_cdds],
    620   enum TALER_ErrorCode *ec)
    621 {
    622   struct TALER_EXCHANGE_PostBatchDepositHandle *dh;
    623   json_t *deposits;
    624   const struct GNUNET_HashCode *wallet_data_hashp;
    625 
    626   if (0 == num_cdds)
    627   {
    628     GNUNET_break (0);
    629     *ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    630     return NULL;
    631   }
    632   if (GNUNET_TIME_timestamp_cmp (dcd->refund_deadline,
    633                                  >,
    634                                  dcd->wire_deadline))
    635   {
    636     GNUNET_break_op (0);
    637     *ec = TALER_EC_EXCHANGE_DEPOSIT_REFUND_DEADLINE_AFTER_WIRE_DEADLINE;
    638     return NULL;
    639   }
    640   dh = GNUNET_new (struct TALER_EXCHANGE_PostBatchDepositHandle);
    641   dh->auditor_chance = AUDITOR_CHANCE;
    642   dh->ctx = ctx;
    643   dh->base_url = GNUNET_strdup (url);
    644   dh->cdds = GNUNET_memdup (cdds,
    645                             num_cdds * sizeof (*cdds));
    646   dh->num_cdds = num_cdds;
    647   dh->dcd = *dcd;
    648   if (NULL != dcd->policy_details)
    649     TALER_deposit_policy_hash (dcd->policy_details,
    650                                &dh->h_policy);
    651   TALER_merchant_wire_signature_hash (dcd->merchant_payto_uri,
    652                                       &dcd->wire_salt,
    653                                       &dh->h_wire);
    654   deposits = json_array ();
    655   GNUNET_assert (NULL != deposits);
    656   GNUNET_assert (GNUNET_OK ==
    657                  TALER_amount_set_zero (cdds[0].amount.currency,
    658                                         &dh->total_without_fee));
    659   for (unsigned int i = 0; i<num_cdds; i++)
    660   {
    661     const struct TALER_EXCHANGE_CoinDepositDetail *cdd = &cdds[i];
    662     const struct TALER_EXCHANGE_DenomPublicKey *dki;
    663     const struct TALER_AgeCommitmentHashP *h_age_commitmentp;
    664     struct TALER_Amount amount_without_fee;
    665 
    666     dki = TALER_EXCHANGE_get_denomination_key_by_hash (keys,
    667                                                        &cdd->h_denom_pub);
    668     if (NULL == dki)
    669     {
    670       *ec = TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
    671       GNUNET_break_op (0);
    672       json_decref (deposits);
    673       GNUNET_free (dh->base_url);
    674       GNUNET_free (dh->cdds);
    675       GNUNET_free (dh);
    676       return NULL;
    677     }
    678     if (0 >
    679         TALER_amount_subtract (&amount_without_fee,
    680                                &cdd->amount,
    681                                &dki->fees.deposit))
    682     {
    683       *ec = TALER_EC_EXCHANGE_DEPOSIT_FEE_ABOVE_AMOUNT;
    684       GNUNET_break_op (0);
    685       json_decref (deposits);
    686       GNUNET_free (dh->base_url);
    687       GNUNET_free (dh->cdds);
    688       GNUNET_free (dh);
    689       return NULL;
    690     }
    691     GNUNET_assert (0 <=
    692                    TALER_amount_add (&dh->total_without_fee,
    693                                      &dh->total_without_fee,
    694                                      &amount_without_fee));
    695     if (GNUNET_OK !=
    696         TALER_EXCHANGE_verify_deposit_signature_ (dcd,
    697                                                   &dh->h_policy,
    698                                                   &dh->h_wire,
    699                                                   cdd,
    700                                                   dki))
    701     {
    702       *ec = TALER_EC_EXCHANGE_DEPOSIT_COIN_SIGNATURE_INVALID;
    703       GNUNET_break_op (0);
    704       json_decref (deposits);
    705       GNUNET_free (dh->base_url);
    706       GNUNET_free (dh->cdds);
    707       GNUNET_free (dh);
    708       return NULL;
    709     }
    710     if (! GNUNET_is_zero (&dcd->merchant_sig))
    711     {
    712       /* FIXME #9185: check merchant_sig!? */
    713     }
    714     if (GNUNET_is_zero (&cdd->h_age_commitment))
    715       h_age_commitmentp = NULL;
    716     else
    717       h_age_commitmentp = &cdd->h_age_commitment;
    718     GNUNET_assert (
    719       0 ==
    720       json_array_append_new (
    721         deposits,
    722         GNUNET_JSON_PACK (
    723           TALER_JSON_pack_amount ("contribution",
    724                                   &cdd->amount),
    725           GNUNET_JSON_pack_data_auto ("denom_pub_hash",
    726                                       &cdd->h_denom_pub),
    727           TALER_JSON_pack_denom_sig ("ub_sig",
    728                                      &cdd->denom_sig),
    729           GNUNET_JSON_pack_data_auto ("coin_pub",
    730                                       &cdd->coin_pub),
    731           GNUNET_JSON_pack_allow_null (
    732             GNUNET_JSON_pack_data_auto ("h_age_commitment",
    733                                         h_age_commitmentp)),
    734           GNUNET_JSON_pack_data_auto ("coin_sig",
    735                                       &cdd->coin_sig)
    736           )));
    737   }
    738 
    739   if (GNUNET_is_zero (&dcd->wallet_data_hash))
    740     wallet_data_hashp = NULL;
    741   else
    742     wallet_data_hashp = &dcd->wallet_data_hash;
    743   dh->deposit_obj = GNUNET_JSON_PACK (
    744     TALER_JSON_pack_full_payto ("merchant_payto_uri",
    745                                 dcd->merchant_payto_uri),
    746     GNUNET_JSON_pack_allow_null (
    747       GNUNET_JSON_pack_string ("extra_wire_subject_metadata",
    748                                dcd->extra_wire_subject_metadata)),
    749     GNUNET_JSON_pack_data_auto ("wire_salt",
    750                                 &dcd->wire_salt),
    751     GNUNET_JSON_pack_data_auto ("h_contract_terms",
    752                                 &dcd->h_contract_terms),
    753     GNUNET_JSON_pack_array_steal ("coins",
    754                                   deposits),
    755     GNUNET_JSON_pack_allow_null (
    756       GNUNET_JSON_pack_data_auto ("wallet_data_hash",
    757                                   wallet_data_hashp)),
    758     GNUNET_JSON_pack_allow_null (
    759       GNUNET_JSON_pack_object_steal ("policy_details",
    760                                      (json_t *) dcd->policy_details)),
    761     GNUNET_JSON_pack_timestamp ("timestamp",
    762                                 dcd->wallet_timestamp),
    763     GNUNET_JSON_pack_data_auto ("merchant_pub",
    764                                 &dcd->merchant_pub),
    765     GNUNET_JSON_pack_data_auto ("merchant_sig",
    766                                 &dcd->merchant_sig),
    767     GNUNET_JSON_pack_allow_null (
    768       GNUNET_JSON_pack_timestamp ("refund_deadline",
    769                                   dcd->refund_deadline)),
    770     GNUNET_JSON_pack_timestamp ("wire_transfer_deadline",
    771                                 dcd->wire_deadline));
    772   if (NULL == dh->deposit_obj)
    773   {
    774     GNUNET_break (0);
    775     *ec = TALER_EC_GENERIC_ALLOCATION_FAILURE;
    776     GNUNET_free (dh->base_url);
    777     GNUNET_free (dh->cdds);
    778     GNUNET_free (dh);
    779     return NULL;
    780   }
    781   dh->keys = TALER_EXCHANGE_keys_incref (keys);
    782   *ec = TALER_EC_NONE;
    783   return dh;
    784 }
    785 
    786 
    787 void
    788 TALER_EXCHANGE_post_batch_deposit_force_dc (
    789   struct TALER_EXCHANGE_PostBatchDepositHandle *pbdh)
    790 {
    791   // FIXME: turn this into an option!
    792   pbdh->auditor_chance = 1;
    793 }
    794 
    795 
    796 enum TALER_ErrorCode
    797 TALER_EXCHANGE_post_batch_deposit_start (
    798   struct TALER_EXCHANGE_PostBatchDepositHandle *pbdh,
    799   TALER_EXCHANGE_PostBatchDepositCallback cb,
    800   TALER_EXCHANGE_POST_BATCH_DEPOSIT_RESULT_CLOSURE *cb_cls)
    801 {
    802   CURL *eh;
    803 
    804   pbdh->cb = cb;
    805   pbdh->cb_cls = cb_cls;
    806   pbdh->url = TALER_url_join (pbdh->base_url,
    807                               "batch-deposit",
    808                               NULL);
    809   if (NULL == pbdh->url)
    810   {
    811     GNUNET_break (0);
    812     return TALER_EC_GENERIC_ALLOCATION_FAILURE;
    813   }
    814   eh = TALER_EXCHANGE_curl_easy_get_ (pbdh->url);
    815   if ( (NULL == eh) ||
    816        (GNUNET_OK !=
    817         TALER_curl_easy_post (&pbdh->post_ctx,
    818                               eh,
    819                               pbdh->deposit_obj)) )
    820   {
    821     GNUNET_break (0);
    822     if (NULL != eh)
    823       curl_easy_cleanup (eh);
    824     return TALER_EC_GENERIC_CURL_ALLOCATION_FAILURE;
    825   }
    826   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    827               "URL for batch-deposit: `%s'\n",
    828               pbdh->url);
    829   pbdh->job = GNUNET_CURL_job_add2 (pbdh->ctx,
    830                                     eh,
    831                                     pbdh->post_ctx.headers,
    832                                     &handle_deposit_finished,
    833                                     pbdh);
    834   if (NULL == pbdh->job)
    835     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    836   return TALER_EC_NONE;
    837 }
    838 
    839 
    840 void
    841 TALER_EXCHANGE_post_batch_deposit_cancel (
    842   struct TALER_EXCHANGE_PostBatchDepositHandle *pbdh)
    843 {
    844   struct TEAH_AuditorInteractionEntry *aie;
    845 
    846   while (NULL != (aie = pbdh->ai_head))
    847   {
    848     GNUNET_assert (aie->dh == pbdh);
    849     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    850                 "Not sending deposit confirmation to auditor `%s' due to cancellation\n",
    851                 aie->auditor_url);
    852     TALER_AUDITOR_deposit_confirmation_cancel (aie->dch);
    853     GNUNET_CONTAINER_DLL_remove (pbdh->ai_head,
    854                                  pbdh->ai_tail,
    855                                  aie);
    856     GNUNET_free (aie);
    857   }
    858   if (NULL != pbdh->job)
    859   {
    860     GNUNET_CURL_job_cancel (pbdh->job);
    861     pbdh->job = NULL;
    862   }
    863   TALER_EXCHANGE_keys_decref (pbdh->keys);
    864   GNUNET_free (pbdh->base_url);
    865   GNUNET_free (pbdh->url);
    866   GNUNET_free (pbdh->cdds);
    867   TALER_curl_easy_post_finished (&pbdh->post_ctx);
    868   json_decref (pbdh->deposit_obj);
    869   json_decref (pbdh->response);
    870   GNUNET_free (pbdh);
    871 }
    872 
    873 
    874 /* end of exchange_api_post-batch-deposit.c */