Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2018-2024 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it
6 : under the terms of the GNU General Public License as published by
7 : the Free Software Foundation; either version 3, or (at your
8 : option) any later version.
9 :
10 : TALER is distributed in the hope that it will be useful, but
11 : WITHOUT ANY WARRANTY; without even the implied warranty of
12 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 : General Public License for more details.
14 :
15 : You should have received a copy of the GNU General Public
16 : License along with TALER; see the file COPYING. If not, see
17 : <http://www.gnu.org/licenses/>
18 : */
19 : /**
20 : * @file testing/testing_api_cmd_batch_deposit.c
21 : * @brief command for testing /batch-deposit.
22 : * @author Marcello Stanisci
23 : * @author Christian Grothoff
24 : */
25 : #include "platform.h"
26 : #include "taler_json_lib.h"
27 : #include <gnunet/gnunet_curl_lib.h>
28 : #include "taler_testing_lib.h"
29 : #include "taler_signatures.h"
30 : #include "backoff.h"
31 :
32 :
33 : /**
34 : * How often do we retry before giving up?
35 : */
36 : #define NUM_RETRIES 5
37 :
38 : /**
39 : * How long do we wait AT MOST when retrying?
40 : */
41 : #define MAX_BACKOFF GNUNET_TIME_relative_multiply ( \
42 : GNUNET_TIME_UNIT_MILLISECONDS, 100)
43 :
44 :
45 : /**
46 : * Information per coin in the batch.
47 : */
48 : struct Coin
49 : {
50 :
51 : /**
52 : * Amount to deposit.
53 : */
54 : struct TALER_Amount amount;
55 :
56 : /**
57 : * Deposit fee.
58 : */
59 : struct TALER_Amount deposit_fee;
60 :
61 : /**
62 : * Our coin signature.
63 : */
64 : struct TALER_CoinSpendSignatureP coin_sig;
65 :
66 : /**
67 : * Reference to any command that is able to provide a coin,
68 : * possibly using $LABEL#$INDEX notation.
69 : */
70 : char *coin_reference;
71 :
72 : /**
73 : * Denomination public key of the coin.
74 : */
75 : const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
76 :
77 : /**
78 : * The command being referenced.
79 : */
80 : const struct TALER_TESTING_Command *coin_cmd;
81 :
82 : /**
83 : * Expected entry in the coin history created by this
84 : * coin.
85 : */
86 : struct TALER_EXCHANGE_CoinHistoryEntry che;
87 :
88 : /**
89 : * Index of the coin at @e coin_cmd.
90 : */
91 : unsigned int coin_idx;
92 : };
93 :
94 :
95 : /**
96 : * State for a "batch deposit" CMD.
97 : */
98 : struct BatchDepositState
99 : {
100 :
101 : /**
102 : * Refund deadline. Zero for no refunds.
103 : */
104 : struct GNUNET_TIME_Timestamp refund_deadline;
105 :
106 : /**
107 : * Wire deadline.
108 : */
109 : struct GNUNET_TIME_Timestamp wire_deadline;
110 :
111 : /**
112 : * Timestamp of the /deposit operation in the wallet (contract signing time).
113 : */
114 : struct GNUNET_TIME_Timestamp wallet_timestamp;
115 :
116 : /**
117 : * How long do we wait until we retry?
118 : */
119 : struct GNUNET_TIME_Relative backoff;
120 :
121 : /**
122 : * When did the exchange receive the deposit?
123 : */
124 : struct GNUNET_TIME_Timestamp exchange_timestamp;
125 :
126 : /**
127 : * Signing key used by the exchange to sign the
128 : * deposit confirmation.
129 : */
130 : struct TALER_ExchangePublicKeyP exchange_pub;
131 :
132 : /**
133 : * Set (by the interpreter) to a fresh private key. This
134 : * key will be used to sign the deposit request.
135 : */
136 : union TALER_AccountPrivateKeyP account_priv;
137 :
138 : /**
139 : * Set (by the interpreter) to the public key
140 : * corresponding to @e account_priv.
141 : */
142 : union TALER_AccountPublicKeyP account_pub;
143 :
144 : /**
145 : * Deposit handle while operation is running.
146 : */
147 : struct TALER_EXCHANGE_BatchDepositHandle *dh;
148 :
149 : /**
150 : * Array of coins to batch-deposit.
151 : */
152 : struct Coin *coins;
153 :
154 : /**
155 : * Wire details of who is depositing -- this would be merchant
156 : * wire details in a normal scenario.
157 : */
158 : json_t *wire_details;
159 :
160 : /**
161 : * JSON string describing what a proposal is about.
162 : */
163 : json_t *contract_terms;
164 :
165 : /**
166 : * Interpreter state.
167 : */
168 : struct TALER_TESTING_Interpreter *is;
169 :
170 : /**
171 : * Task scheduled to try later.
172 : */
173 : struct GNUNET_SCHEDULER_Task *retry_task;
174 :
175 : /**
176 : * Deposit confirmation signature from the exchange.
177 : */
178 : struct TALER_ExchangeSignatureP exchange_sig;
179 :
180 : /**
181 : * Set to the KYC requirement payto hash *if* the exchange replied with a
182 : * request for KYC.
183 : */
184 : struct TALER_NormalizedPaytoHashP h_payto;
185 :
186 : /**
187 : * Set to the KYC requirement row *if* the exchange replied with
188 : * a request for KYC.
189 : */
190 : uint64_t requirement_row;
191 :
192 : /**
193 : * Reference to previous deposit operation.
194 : * Only present if we're supposed to replay the previous deposit.
195 : */
196 : const char *deposit_reference;
197 :
198 : /**
199 : * If @e coin_reference refers to an operation that generated
200 : * an array of coins, this value determines which coin to pick.
201 : */
202 : unsigned int num_coins;
203 :
204 : /**
205 : * Expected HTTP response code.
206 : */
207 : unsigned int expected_response_code;
208 :
209 : /**
210 : * Set to true if the /deposit succeeded
211 : * and we now can provide the resulting traits.
212 : */
213 : bool deposit_succeeded;
214 :
215 : };
216 :
217 :
218 : /**
219 : * Callback to analyze the /batch-deposit response, just used to check if the
220 : * response code is acceptable.
221 : *
222 : * @param cls closure.
223 : * @param dr deposit response details
224 : */
225 : static void
226 2 : batch_deposit_cb (void *cls,
227 : const struct TALER_EXCHANGE_BatchDepositResult *dr)
228 : {
229 2 : struct BatchDepositState *ds = cls;
230 :
231 2 : ds->dh = NULL;
232 2 : if (ds->expected_response_code != dr->hr.http_status)
233 : {
234 0 : TALER_TESTING_unexpected_status (ds->is,
235 : dr->hr.http_status,
236 : ds->expected_response_code);
237 0 : return;
238 : }
239 2 : switch (dr->hr.http_status)
240 : {
241 2 : case MHD_HTTP_OK:
242 2 : ds->deposit_succeeded = GNUNET_YES;
243 2 : ds->exchange_timestamp = dr->details.ok.deposit_timestamp;
244 2 : ds->exchange_pub = *dr->details.ok.exchange_pub;
245 2 : ds->exchange_sig = *dr->details.ok.exchange_sig;
246 2 : break;
247 0 : case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
248 : /* nothing to check */
249 : ds->requirement_row
250 0 : = dr->details.unavailable_for_legal_reasons.requirement_row;
251 : ds->h_payto
252 0 : = dr->details.unavailable_for_legal_reasons.h_payto;
253 0 : break;
254 : }
255 2 : TALER_TESTING_interpreter_next (ds->is);
256 : }
257 :
258 :
259 : /**
260 : * Run the command.
261 : *
262 : * @param cls closure.
263 : * @param cmd the command to execute.
264 : * @param is the interpreter state.
265 : */
266 : static void
267 2 : batch_deposit_run (void *cls,
268 : const struct TALER_TESTING_Command *cmd,
269 : struct TALER_TESTING_Interpreter *is)
270 2 : {
271 2 : struct BatchDepositState *ds = cls;
272 : const struct TALER_DenominationSignature *denom_pub_sig;
273 : struct TALER_PrivateContractHashP h_contract_terms;
274 : enum TALER_ErrorCode ec;
275 : struct TALER_WireSaltP wire_salt;
276 : struct TALER_MerchantWireHashP h_wire;
277 : struct TALER_FullPayto payto_uri;
278 2 : struct TALER_EXCHANGE_CoinDepositDetail cdds[ds->num_coins];
279 : struct GNUNET_JSON_Specification spec[] = {
280 2 : TALER_JSON_spec_full_payto_uri ("payto_uri",
281 : &payto_uri),
282 2 : GNUNET_JSON_spec_fixed_auto ("salt",
283 : &wire_salt),
284 2 : GNUNET_JSON_spec_end ()
285 : };
286 : const char *exchange_url
287 2 : = TALER_TESTING_get_exchange_url (is);
288 :
289 : (void) cmd;
290 2 : if (NULL == exchange_url)
291 : {
292 0 : GNUNET_break (0);
293 0 : return;
294 : }
295 2 : memset (cdds,
296 : 0,
297 : sizeof (cdds));
298 2 : ds->is = is;
299 2 : GNUNET_assert (NULL != ds->wire_details);
300 2 : if (GNUNET_OK !=
301 2 : GNUNET_JSON_parse (ds->wire_details,
302 : spec,
303 : NULL, NULL))
304 : {
305 0 : json_dumpf (ds->wire_details,
306 : stderr,
307 : JSON_INDENT (2));
308 0 : GNUNET_break (0);
309 0 : TALER_TESTING_interpreter_fail (is);
310 0 : return;
311 : }
312 : #if DUMP_CONTRACT
313 : fprintf (stderr,
314 : "Using contract:\n");
315 : json_dumpf (ds->contract_terms,
316 : stderr,
317 : JSON_INDENT (2));
318 : #endif
319 2 : if (GNUNET_OK !=
320 2 : TALER_JSON_contract_hash (ds->contract_terms,
321 : &h_contract_terms))
322 : {
323 0 : GNUNET_break (0);
324 0 : TALER_TESTING_interpreter_fail (is);
325 0 : return;
326 : }
327 2 : GNUNET_assert (GNUNET_OK ==
328 : TALER_JSON_merchant_wire_signature_hash (ds->wire_details,
329 : &h_wire));
330 2 : if (! GNUNET_TIME_absolute_is_zero (ds->refund_deadline.abs_time))
331 : {
332 : struct GNUNET_TIME_Relative refund_deadline;
333 :
334 : refund_deadline
335 0 : = GNUNET_TIME_absolute_get_remaining (ds->refund_deadline.abs_time);
336 : ds->wire_deadline
337 : =
338 0 : GNUNET_TIME_relative_to_timestamp (
339 : GNUNET_TIME_relative_multiply (refund_deadline,
340 : 2));
341 : }
342 : else
343 : {
344 2 : ds->refund_deadline = ds->wallet_timestamp;
345 2 : ds->wire_deadline = GNUNET_TIME_timestamp_get ();
346 : }
347 :
348 : {
349 : const struct TALER_TESTING_Command *acc_var;
350 2 : if (NULL != (acc_var
351 2 : = TALER_TESTING_interpreter_get_command (
352 : is,
353 : "account-priv")))
354 : {
355 : const union TALER_AccountPrivateKeyP *account_priv;
356 :
357 2 : if ( (GNUNET_OK !=
358 2 : TALER_TESTING_get_trait_account_priv (acc_var,
359 : &account_priv)) )
360 : {
361 0 : GNUNET_break (0);
362 0 : TALER_TESTING_interpreter_fail (is);
363 0 : return;
364 : }
365 2 : ds->account_priv = *account_priv;
366 2 : GNUNET_CRYPTO_eddsa_key_get_public (
367 2 : &ds->account_priv.merchant_priv.eddsa_priv,
368 : &ds->account_pub.merchant_pub.eddsa_pub);
369 : }
370 : else
371 : {
372 0 : GNUNET_CRYPTO_eddsa_key_create (
373 : &ds->account_priv.merchant_priv.eddsa_priv);
374 0 : GNUNET_CRYPTO_eddsa_key_get_public (
375 0 : &ds->account_priv.merchant_priv.eddsa_priv,
376 : &ds->account_pub.merchant_pub.eddsa_pub);
377 : }
378 : }
379 6 : for (unsigned int i = 0; i<ds->num_coins; i++)
380 : {
381 4 : struct Coin *coin = &ds->coins[i];
382 4 : struct TALER_EXCHANGE_CoinDepositDetail *cdd = &cdds[i];
383 : const struct TALER_CoinSpendPrivateKeyP *coin_priv;
384 4 : const struct TALER_AgeCommitmentProof *age_commitment_proof = NULL;
385 :
386 4 : GNUNET_assert (NULL != coin->coin_reference);
387 4 : cdd->amount = coin->amount;
388 8 : coin->coin_cmd = TALER_TESTING_interpreter_lookup_command (
389 : is,
390 4 : coin->coin_reference);
391 4 : if (NULL == coin->coin_cmd)
392 : {
393 0 : GNUNET_break (0);
394 0 : TALER_TESTING_interpreter_fail (is);
395 0 : return;
396 : }
397 :
398 4 : if ( (GNUNET_OK !=
399 4 : TALER_TESTING_get_trait_coin_priv (coin->coin_cmd,
400 : coin->coin_idx,
401 4 : &coin_priv)) ||
402 : (GNUNET_OK !=
403 4 : TALER_TESTING_get_trait_age_commitment_proof (coin->coin_cmd,
404 : coin->coin_idx,
405 : &age_commitment_proof))
406 4 : ||
407 : (GNUNET_OK !=
408 4 : TALER_TESTING_get_trait_denom_pub (coin->coin_cmd,
409 : coin->coin_idx,
410 4 : &coin->denom_pub)) ||
411 : (GNUNET_OK !=
412 4 : TALER_TESTING_get_trait_denom_sig (coin->coin_cmd,
413 : coin->coin_idx,
414 : &denom_pub_sig)) )
415 : {
416 0 : GNUNET_break (0);
417 0 : TALER_TESTING_interpreter_fail (is);
418 0 : return;
419 : }
420 4 : if (NULL != age_commitment_proof)
421 : {
422 0 : TALER_age_commitment_hash (&age_commitment_proof->commitment,
423 : &cdd->h_age_commitment);
424 : }
425 4 : coin->deposit_fee = coin->denom_pub->fees.deposit;
426 4 : GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
427 : &cdd->coin_pub.eddsa_pub);
428 4 : cdd->denom_sig = *denom_pub_sig;
429 4 : cdd->h_denom_pub = coin->denom_pub->h_key;
430 4 : TALER_wallet_deposit_sign (&coin->amount,
431 4 : &coin->denom_pub->fees.deposit,
432 : &h_wire,
433 : &h_contract_terms,
434 : NULL, /* wallet_data_hash */
435 4 : &cdd->h_age_commitment,
436 : NULL, /* hash of extensions */
437 4 : &coin->denom_pub->h_key,
438 : ds->wallet_timestamp,
439 4 : &ds->account_pub.merchant_pub,
440 : ds->refund_deadline,
441 : coin_priv,
442 : &cdd->coin_sig);
443 4 : coin->coin_sig = cdd->coin_sig;
444 4 : coin->che.type = TALER_EXCHANGE_CTT_DEPOSIT;
445 4 : coin->che.amount = coin->amount;
446 4 : coin->che.details.deposit.h_wire = h_wire;
447 4 : coin->che.details.deposit.h_contract_terms = h_contract_terms;
448 4 : coin->che.details.deposit.no_h_policy = true;
449 4 : coin->che.details.deposit.no_wallet_data_hash = true;
450 4 : coin->che.details.deposit.wallet_timestamp = ds->wallet_timestamp;
451 4 : coin->che.details.deposit.merchant_pub = ds->account_pub.merchant_pub;
452 4 : coin->che.details.deposit.refund_deadline = ds->refund_deadline;
453 4 : coin->che.details.deposit.sig = cdd->coin_sig;
454 4 : coin->che.details.deposit.no_hac = GNUNET_is_zero (&cdd->h_age_commitment);
455 4 : coin->che.details.deposit.hac = cdd->h_age_commitment;
456 4 : coin->che.details.deposit.deposit_fee = coin->denom_pub->fees.deposit;
457 : }
458 :
459 2 : GNUNET_assert (NULL == ds->dh);
460 : {
461 2 : struct TALER_EXCHANGE_DepositContractDetail dcd = {
462 : .wire_deadline = ds->wire_deadline,
463 : .merchant_payto_uri = payto_uri,
464 : .wire_salt = wire_salt,
465 : .h_contract_terms = h_contract_terms,
466 : .policy_details = NULL /* FIXME #7270-OEC */,
467 : .wallet_timestamp = ds->wallet_timestamp,
468 : .merchant_pub = ds->account_pub.merchant_pub,
469 : .refund_deadline = ds->refund_deadline
470 : };
471 :
472 2 : TALER_merchant_contract_sign (&h_contract_terms,
473 2 : &ds->account_priv.merchant_priv,
474 : &dcd.merchant_sig);
475 2 : ds->dh = TALER_EXCHANGE_batch_deposit (
476 : TALER_TESTING_interpreter_get_context (is),
477 : exchange_url,
478 : TALER_TESTING_get_keys (is),
479 : &dcd,
480 : ds->num_coins,
481 : cdds,
482 : &batch_deposit_cb,
483 : ds,
484 : &ec);
485 : }
486 2 : if (NULL == ds->dh)
487 : {
488 0 : GNUNET_break (0);
489 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
490 : "Could not create deposit with EC %d\n",
491 : (int) ec);
492 0 : TALER_TESTING_interpreter_fail (is);
493 0 : return;
494 : }
495 : }
496 :
497 :
498 : /**
499 : * Free the state of a "batch-deposit" CMD, and possibly cancel a
500 : * pending operation thereof.
501 : *
502 : * @param cls closure, must be a `struct BatchDepositState`.
503 : * @param cmd the command which is being cleaned up.
504 : */
505 : static void
506 2 : batch_deposit_cleanup (void *cls,
507 : const struct TALER_TESTING_Command *cmd)
508 : {
509 2 : struct BatchDepositState *ds = cls;
510 :
511 2 : if (NULL != ds->dh)
512 : {
513 0 : TALER_TESTING_command_incomplete (ds->is,
514 : cmd->label);
515 0 : TALER_EXCHANGE_batch_deposit_cancel (ds->dh);
516 0 : ds->dh = NULL;
517 : }
518 2 : if (NULL != ds->retry_task)
519 : {
520 0 : GNUNET_SCHEDULER_cancel (ds->retry_task);
521 0 : ds->retry_task = NULL;
522 : }
523 6 : for (unsigned int i = 0; i<ds->num_coins; i++)
524 4 : GNUNET_free (ds->coins[i].coin_reference);
525 2 : GNUNET_free (ds->coins);
526 2 : json_decref (ds->wire_details);
527 2 : json_decref (ds->contract_terms);
528 2 : GNUNET_free (ds);
529 2 : }
530 :
531 :
532 : /**
533 : * Offer internal data from a "batch-deposit" CMD, to other commands.
534 : *
535 : * @param cls closure.
536 : * @param[out] ret result.
537 : * @param trait name of the trait.
538 : * @param index index number of the object to offer.
539 : * @return #GNUNET_OK on success.
540 : */
541 : static enum GNUNET_GenericReturnValue
542 8 : batch_deposit_traits (void *cls,
543 : const void **ret,
544 : const char *trait,
545 : unsigned int index)
546 : {
547 8 : struct BatchDepositState *ds = cls;
548 8 : const struct Coin *coin = &ds->coins[index];
549 : /* Will point to coin cmd internals. */
550 : const struct TALER_CoinSpendPrivateKeyP *coin_spent_priv;
551 : struct TALER_CoinSpendPublicKeyP coin_spent_pub;
552 : const struct TALER_AgeCommitmentProof *age_commitment_proof;
553 :
554 8 : if (index >= ds->num_coins)
555 : {
556 2 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
557 : "[batch_deposit_traits] asked for index #%u while num_coins is #%u\n",
558 : index,
559 : ds->num_coins);
560 2 : return GNUNET_NO;
561 : }
562 6 : if (NULL == coin->coin_cmd)
563 : {
564 0 : GNUNET_break (0);
565 0 : TALER_TESTING_interpreter_fail (ds->is);
566 0 : return GNUNET_NO;
567 : }
568 6 : if ( (GNUNET_OK !=
569 6 : TALER_TESTING_get_trait_coin_priv (coin->coin_cmd,
570 6 : coin->coin_idx,
571 6 : &coin_spent_priv)) ||
572 : (GNUNET_OK !=
573 6 : TALER_TESTING_get_trait_age_commitment_proof (coin->coin_cmd,
574 6 : coin->coin_idx,
575 : &age_commitment_proof)) )
576 : {
577 0 : GNUNET_break (0);
578 0 : TALER_TESTING_interpreter_fail (ds->is);
579 0 : return GNUNET_NO;
580 : }
581 :
582 6 : GNUNET_CRYPTO_eddsa_key_get_public (&coin_spent_priv->eddsa_priv,
583 : &coin_spent_pub.eddsa_pub);
584 :
585 : {
586 : struct TALER_TESTING_Trait traits[] = {
587 : /* First two traits are only available if
588 : ds->traits is #GNUNET_YES */
589 6 : TALER_TESTING_make_trait_exchange_pub (0,
590 6 : &ds->exchange_pub),
591 6 : TALER_TESTING_make_trait_exchange_sig (0,
592 6 : &ds->exchange_sig),
593 : /* These traits are always available */
594 6 : TALER_TESTING_make_trait_wire_details (ds->wire_details),
595 6 : TALER_TESTING_make_trait_contract_terms (ds->contract_terms),
596 6 : TALER_TESTING_make_trait_merchant_priv (&ds->account_priv.merchant_priv),
597 6 : TALER_TESTING_make_trait_merchant_pub (&ds->account_pub.merchant_pub),
598 6 : TALER_TESTING_make_trait_account_priv (&ds->account_priv),
599 6 : TALER_TESTING_make_trait_account_pub (&ds->account_pub),
600 6 : TALER_TESTING_make_trait_age_commitment_proof (index,
601 : age_commitment_proof),
602 6 : TALER_TESTING_make_trait_coin_history (index,
603 : &coin->che),
604 6 : TALER_TESTING_make_trait_coin_pub (index,
605 : &coin_spent_pub),
606 6 : TALER_TESTING_make_trait_denom_pub (index,
607 6 : coin->denom_pub),
608 6 : TALER_TESTING_make_trait_coin_priv (index,
609 : coin_spent_priv),
610 6 : TALER_TESTING_make_trait_coin_sig (index,
611 : &coin->coin_sig),
612 6 : TALER_TESTING_make_trait_deposit_amount (index,
613 : &coin->amount),
614 6 : TALER_TESTING_make_trait_deposit_fee_amount (index,
615 : &coin->deposit_fee),
616 6 : TALER_TESTING_make_trait_timestamp (index,
617 6 : &ds->exchange_timestamp),
618 6 : TALER_TESTING_make_trait_wire_deadline (index,
619 6 : &ds->wire_deadline),
620 6 : TALER_TESTING_make_trait_refund_deadline (index,
621 6 : &ds->refund_deadline),
622 6 : TALER_TESTING_make_trait_legi_requirement_row (&ds->requirement_row),
623 6 : TALER_TESTING_make_trait_h_normalized_payto (&ds->h_payto),
624 6 : TALER_TESTING_trait_end ()
625 : };
626 :
627 6 : return TALER_TESTING_get_trait ((ds->deposit_succeeded)
628 : ? traits
629 : : &traits[2],
630 : ret,
631 : trait,
632 : index);
633 : }
634 : }
635 :
636 :
637 : struct TALER_TESTING_Command
638 2 : TALER_TESTING_cmd_batch_deposit (
639 : const char *label,
640 : const struct TALER_FullPayto target_account_payto,
641 : const char *contract_terms,
642 : struct GNUNET_TIME_Relative refund_deadline,
643 : unsigned int expected_response_code,
644 : ...)
645 : {
646 : struct BatchDepositState *ds;
647 : va_list ap;
648 2 : unsigned int num_coins = 0;
649 : const char *ref;
650 :
651 2 : va_start (ap,
652 : expected_response_code);
653 6 : while (NULL != (ref = va_arg (ap,
654 : const char *)))
655 : {
656 4 : GNUNET_assert (NULL != va_arg (ap,
657 : const char *));
658 4 : num_coins++;
659 : }
660 2 : va_end (ap);
661 :
662 2 : ds = GNUNET_new (struct BatchDepositState);
663 2 : ds->num_coins = num_coins;
664 2 : ds->coins = GNUNET_new_array (num_coins,
665 : struct Coin);
666 2 : num_coins = 0;
667 2 : va_start (ap,
668 : expected_response_code);
669 6 : while (NULL != (ref = va_arg (ap,
670 : const char *)))
671 : {
672 4 : struct Coin *coin = &ds->coins[num_coins++];
673 4 : const char *amount = va_arg (ap,
674 : const char *);
675 :
676 4 : GNUNET_assert (GNUNET_OK ==
677 : TALER_TESTING_parse_coin_reference (ref,
678 : &coin->coin_reference,
679 : &coin->coin_idx));
680 4 : GNUNET_assert (GNUNET_OK ==
681 : TALER_string_to_amount (amount,
682 : &coin->amount));
683 : }
684 2 : va_end (ap);
685 :
686 2 : ds->wire_details = TALER_TESTING_make_wire_details (target_account_payto);
687 2 : GNUNET_assert (NULL != ds->wire_details);
688 2 : ds->contract_terms = json_loads (contract_terms,
689 : JSON_REJECT_DUPLICATES,
690 : NULL);
691 2 : if (NULL == ds->contract_terms)
692 : {
693 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
694 : "Failed to parse contract terms `%s' for CMD `%s'\n",
695 : contract_terms,
696 : label);
697 0 : GNUNET_assert (0);
698 : }
699 2 : ds->wallet_timestamp = GNUNET_TIME_timestamp_get ();
700 2 : GNUNET_assert (0 ==
701 : json_object_set_new (ds->contract_terms,
702 : "timestamp",
703 : GNUNET_JSON_from_timestamp (
704 : ds->wallet_timestamp)));
705 2 : if (! GNUNET_TIME_relative_is_zero (refund_deadline))
706 : {
707 0 : ds->refund_deadline = GNUNET_TIME_relative_to_timestamp (refund_deadline);
708 0 : GNUNET_assert (0 ==
709 : json_object_set_new (ds->contract_terms,
710 : "refund_deadline",
711 : GNUNET_JSON_from_timestamp (
712 : ds->refund_deadline)));
713 : }
714 2 : ds->expected_response_code = expected_response_code;
715 : {
716 2 : struct TALER_TESTING_Command cmd = {
717 : .cls = ds,
718 : .label = label,
719 : .run = &batch_deposit_run,
720 : .cleanup = &batch_deposit_cleanup,
721 : .traits = &batch_deposit_traits
722 : };
723 :
724 2 : return cmd;
725 : }
726 : }
727 :
728 :
729 : /* end of testing_api_cmd_batch_deposit.c */
|