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 (26360B)


      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     dr->hr.ec = TALER_JSON_get_error_code (j);
    564     dr->hr.hint = TALER_JSON_get_error_hint (j);
    565     if (GNUNET_OK !=
    566         TALER_EXCHANGE_parse_451 (&dr->details.unavailable_for_legal_reasons,
    567                                   j))
    568     {
    569       GNUNET_break_op (0);
    570       dr->hr.http_status = 0;
    571       dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    572       break;
    573     }
    574     break;
    575   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    576     dr->hr.ec = TALER_JSON_get_error_code (j);
    577     dr->hr.hint = TALER_JSON_get_error_hint (j);
    578     /* Server had an internal issue; we should retry, but this API
    579        leaves this to the application */
    580     break;
    581   default:
    582     /* unexpected response code */
    583     dr->hr.ec = TALER_JSON_get_error_code (j);
    584     dr->hr.hint = TALER_JSON_get_error_hint (j);
    585     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    586                 "Unexpected response code %u/%d for exchange deposit\n",
    587                 (unsigned int) response_code,
    588                 dr->hr.ec);
    589     GNUNET_break_op (0);
    590     break;
    591   }
    592   if (NULL != dh->ai_head)
    593     return;
    594   finish_dh (dh);
    595 }
    596 
    597 
    598 struct TALER_EXCHANGE_PostBatchDepositHandle *
    599 TALER_EXCHANGE_post_batch_deposit_create (
    600   struct GNUNET_CURL_Context *ctx,
    601   const char *url,
    602   struct TALER_EXCHANGE_Keys *keys,
    603   const struct TALER_EXCHANGE_DepositContractDetail *dcd,
    604   unsigned int num_cdds,
    605   const struct TALER_EXCHANGE_CoinDepositDetail cdds[static num_cdds],
    606   enum TALER_ErrorCode *ec)
    607 {
    608   struct TALER_EXCHANGE_PostBatchDepositHandle *dh;
    609   json_t *deposits;
    610   const struct GNUNET_HashCode *wallet_data_hashp;
    611 
    612   if (0 == num_cdds)
    613   {
    614     GNUNET_break (0);
    615     *ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    616     return NULL;
    617   }
    618   if (GNUNET_TIME_timestamp_cmp (dcd->refund_deadline,
    619                                  >,
    620                                  dcd->wire_deadline))
    621   {
    622     GNUNET_break_op (0);
    623     *ec = TALER_EC_EXCHANGE_DEPOSIT_REFUND_DEADLINE_AFTER_WIRE_DEADLINE;
    624     return NULL;
    625   }
    626   dh = GNUNET_new (struct TALER_EXCHANGE_PostBatchDepositHandle);
    627   dh->auditor_chance = AUDITOR_CHANCE;
    628   dh->ctx = ctx;
    629   dh->base_url = GNUNET_strdup (url);
    630   dh->cdds = GNUNET_memdup (cdds,
    631                             num_cdds * sizeof (*cdds));
    632   dh->num_cdds = num_cdds;
    633   dh->dcd = *dcd;
    634   if (NULL != dcd->policy_details)
    635     TALER_deposit_policy_hash (dcd->policy_details,
    636                                &dh->h_policy);
    637   TALER_merchant_wire_signature_hash (dcd->merchant_payto_uri,
    638                                       &dcd->wire_salt,
    639                                       &dh->h_wire);
    640   deposits = json_array ();
    641   GNUNET_assert (NULL != deposits);
    642   GNUNET_assert (GNUNET_OK ==
    643                  TALER_amount_set_zero (cdds[0].amount.currency,
    644                                         &dh->total_without_fee));
    645   for (unsigned int i = 0; i<num_cdds; i++)
    646   {
    647     const struct TALER_EXCHANGE_CoinDepositDetail *cdd = &cdds[i];
    648     const struct TALER_EXCHANGE_DenomPublicKey *dki;
    649     const struct TALER_AgeCommitmentHashP *h_age_commitmentp;
    650     struct TALER_Amount amount_without_fee;
    651 
    652     dki = TALER_EXCHANGE_get_denomination_key_by_hash (keys,
    653                                                        &cdd->h_denom_pub);
    654     if (NULL == dki)
    655     {
    656       *ec = TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
    657       GNUNET_break_op (0);
    658       json_decref (deposits);
    659       GNUNET_free (dh->base_url);
    660       GNUNET_free (dh->cdds);
    661       GNUNET_free (dh);
    662       return NULL;
    663     }
    664     if (0 >
    665         TALER_amount_subtract (&amount_without_fee,
    666                                &cdd->amount,
    667                                &dki->fees.deposit))
    668     {
    669       *ec = TALER_EC_EXCHANGE_DEPOSIT_FEE_ABOVE_AMOUNT;
    670       GNUNET_break_op (0);
    671       json_decref (deposits);
    672       GNUNET_free (dh->base_url);
    673       GNUNET_free (dh->cdds);
    674       GNUNET_free (dh);
    675       return NULL;
    676     }
    677     GNUNET_assert (0 <=
    678                    TALER_amount_add (&dh->total_without_fee,
    679                                      &dh->total_without_fee,
    680                                      &amount_without_fee));
    681     if (GNUNET_OK !=
    682         TALER_EXCHANGE_verify_deposit_signature_ (dcd,
    683                                                   &dh->h_policy,
    684                                                   &dh->h_wire,
    685                                                   cdd,
    686                                                   dki))
    687     {
    688       *ec = TALER_EC_EXCHANGE_DEPOSIT_COIN_SIGNATURE_INVALID;
    689       GNUNET_break_op (0);
    690       json_decref (deposits);
    691       GNUNET_free (dh->base_url);
    692       GNUNET_free (dh->cdds);
    693       GNUNET_free (dh);
    694       return NULL;
    695     }
    696     if (! GNUNET_is_zero (&dcd->merchant_sig))
    697     {
    698       /* FIXME #9185: check merchant_sig!? */
    699     }
    700     if (GNUNET_is_zero (&cdd->h_age_commitment))
    701       h_age_commitmentp = NULL;
    702     else
    703       h_age_commitmentp = &cdd->h_age_commitment;
    704     GNUNET_assert (
    705       0 ==
    706       json_array_append_new (
    707         deposits,
    708         GNUNET_JSON_PACK (
    709           TALER_JSON_pack_amount ("contribution",
    710                                   &cdd->amount),
    711           GNUNET_JSON_pack_data_auto ("denom_pub_hash",
    712                                       &cdd->h_denom_pub),
    713           TALER_JSON_pack_denom_sig ("ub_sig",
    714                                      &cdd->denom_sig),
    715           GNUNET_JSON_pack_data_auto ("coin_pub",
    716                                       &cdd->coin_pub),
    717           GNUNET_JSON_pack_allow_null (
    718             GNUNET_JSON_pack_data_auto ("h_age_commitment",
    719                                         h_age_commitmentp)),
    720           GNUNET_JSON_pack_data_auto ("coin_sig",
    721                                       &cdd->coin_sig)
    722           )));
    723   }
    724 
    725   if (GNUNET_is_zero (&dcd->wallet_data_hash))
    726     wallet_data_hashp = NULL;
    727   else
    728     wallet_data_hashp = &dcd->wallet_data_hash;
    729   dh->deposit_obj = GNUNET_JSON_PACK (
    730     TALER_JSON_pack_full_payto ("merchant_payto_uri",
    731                                 dcd->merchant_payto_uri),
    732     GNUNET_JSON_pack_allow_null (
    733       GNUNET_JSON_pack_string ("extra_wire_subject_metadata",
    734                                dcd->extra_wire_subject_metadata)),
    735     GNUNET_JSON_pack_data_auto ("wire_salt",
    736                                 &dcd->wire_salt),
    737     GNUNET_JSON_pack_data_auto ("h_contract_terms",
    738                                 &dcd->h_contract_terms),
    739     GNUNET_JSON_pack_array_steal ("coins",
    740                                   deposits),
    741     GNUNET_JSON_pack_allow_null (
    742       GNUNET_JSON_pack_data_auto ("wallet_data_hash",
    743                                   wallet_data_hashp)),
    744     GNUNET_JSON_pack_allow_null (
    745       GNUNET_JSON_pack_object_steal ("policy",
    746                                      (json_t *) dcd->policy_details)),
    747     GNUNET_JSON_pack_timestamp ("timestamp",
    748                                 dcd->wallet_timestamp),
    749     GNUNET_JSON_pack_data_auto ("merchant_pub",
    750                                 &dcd->merchant_pub),
    751     GNUNET_JSON_pack_data_auto ("merchant_sig",
    752                                 &dcd->merchant_sig),
    753     GNUNET_JSON_pack_allow_null (
    754       GNUNET_JSON_pack_timestamp ("refund_deadline",
    755                                   dcd->refund_deadline)),
    756     GNUNET_JSON_pack_timestamp ("wire_transfer_deadline",
    757                                 dcd->wire_deadline));
    758   if (NULL == dh->deposit_obj)
    759   {
    760     GNUNET_break (0);
    761     *ec = TALER_EC_GENERIC_ALLOCATION_FAILURE;
    762     GNUNET_free (dh->base_url);
    763     GNUNET_free (dh->cdds);
    764     GNUNET_free (dh);
    765     return NULL;
    766   }
    767   dh->keys = TALER_EXCHANGE_keys_incref (keys);
    768   *ec = TALER_EC_NONE;
    769   return dh;
    770 }
    771 
    772 
    773 void
    774 TALER_EXCHANGE_post_batch_deposit_force_dc (
    775   struct TALER_EXCHANGE_PostBatchDepositHandle *pbdh)
    776 {
    777   // FIXME: turn this into an option!
    778   pbdh->auditor_chance = 1;
    779 }
    780 
    781 
    782 enum TALER_ErrorCode
    783 TALER_EXCHANGE_post_batch_deposit_start (
    784   struct TALER_EXCHANGE_PostBatchDepositHandle *pbdh,
    785   TALER_EXCHANGE_PostBatchDepositCallback cb,
    786   TALER_EXCHANGE_POST_BATCH_DEPOSIT_RESULT_CLOSURE *cb_cls)
    787 {
    788   CURL *eh;
    789 
    790   pbdh->cb = cb;
    791   pbdh->cb_cls = cb_cls;
    792   pbdh->url = TALER_url_join (pbdh->base_url,
    793                               "batch-deposit",
    794                               NULL);
    795   if (NULL == pbdh->url)
    796   {
    797     GNUNET_break (0);
    798     return TALER_EC_GENERIC_ALLOCATION_FAILURE;
    799   }
    800   eh = TALER_EXCHANGE_curl_easy_get_ (pbdh->url);
    801   if ( (NULL == eh) ||
    802        (GNUNET_OK !=
    803         TALER_curl_easy_post (&pbdh->post_ctx,
    804                               eh,
    805                               pbdh->deposit_obj)) )
    806   {
    807     GNUNET_break (0);
    808     if (NULL != eh)
    809       curl_easy_cleanup (eh);
    810     return TALER_EC_GENERIC_CURL_ALLOCATION_FAILURE;
    811   }
    812   /* Help debug #11305 */
    813   GNUNET_assert (CURLE_OK ==
    814                  curl_easy_setopt (eh,
    815                                    CURLOPT_VERBOSE,
    816                                    1));
    817   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    818               "URL for batch-deposit: `%s'\n",
    819               pbdh->url);
    820   pbdh->job = GNUNET_CURL_job_add2 (pbdh->ctx,
    821                                     eh,
    822                                     pbdh->post_ctx.headers,
    823                                     &handle_deposit_finished,
    824                                     pbdh);
    825   if (NULL == pbdh->job)
    826     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    827   return TALER_EC_NONE;
    828 }
    829 
    830 
    831 void
    832 TALER_EXCHANGE_post_batch_deposit_cancel (
    833   struct TALER_EXCHANGE_PostBatchDepositHandle *pbdh)
    834 {
    835   struct TEAH_AuditorInteractionEntry *aie;
    836 
    837   while (NULL != (aie = pbdh->ai_head))
    838   {
    839     GNUNET_assert (aie->dh == pbdh);
    840     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    841                 "Not sending deposit confirmation to auditor `%s' due to cancellation\n",
    842                 aie->auditor_url);
    843     TALER_AUDITOR_deposit_confirmation_cancel (aie->dch);
    844     GNUNET_CONTAINER_DLL_remove (pbdh->ai_head,
    845                                  pbdh->ai_tail,
    846                                  aie);
    847     GNUNET_free (aie);
    848   }
    849   if (NULL != pbdh->job)
    850   {
    851     GNUNET_CURL_job_cancel (pbdh->job);
    852     pbdh->job = NULL;
    853   }
    854   TALER_EXCHANGE_keys_decref (pbdh->keys);
    855   GNUNET_free (pbdh->base_url);
    856   GNUNET_free (pbdh->url);
    857   GNUNET_free (pbdh->cdds);
    858   TALER_curl_easy_post_finished (&pbdh->post_ctx);
    859   json_decref (pbdh->deposit_obj);
    860   json_decref (pbdh->response);
    861   GNUNET_free (pbdh);
    862 }
    863 
    864 
    865 /* end of exchange_api_post-batch-deposit.c */