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