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


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