Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2022, 2023 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_purse_deposit.c
21 : * @brief command for testing /purses/$PID/create
22 : * @author Christian Grothoff
23 : */
24 : #include "platform.h"
25 : #include "taler_json_lib.h"
26 : #include <gnunet/gnunet_curl_lib.h>
27 : #include "taler_testing_lib.h"
28 : #include "taler_signatures.h"
29 : #include "backoff.h"
30 :
31 : /**
32 : * Information we keep per deposited coin.
33 : */
34 : struct Coin
35 : {
36 : /**
37 : * Reference to the respective command.
38 : */
39 : char *command_ref;
40 :
41 : /**
42 : * Entry in the coin's history generated by this operation.
43 : */
44 : struct TALER_EXCHANGE_CoinHistoryEntry che;
45 :
46 : /**
47 : * Public key of the deposited coin.
48 : */
49 : struct TALER_CoinSpendPublicKeyP coin_pub;
50 :
51 : /**
52 : * index of the specific coin in the traits of @e command_ref.
53 : */
54 : unsigned int coin_index;
55 :
56 : /**
57 : * Amount to deposit (with fee).
58 : */
59 : struct TALER_Amount deposit_with_fee;
60 :
61 : };
62 :
63 :
64 : /**
65 : * State for a "purse deposit" CMD.
66 : */
67 : struct PurseDepositState
68 : {
69 :
70 : /**
71 : * Total purse target amount without fees.
72 : */
73 : struct TALER_Amount target_amount;
74 :
75 : /**
76 : * Reference to any command that is able to provide a coin.
77 : */
78 : struct Coin *coin_references;
79 :
80 : /**
81 : * The purse's public key.
82 : */
83 : struct TALER_PurseContractPublicKeyP purse_pub;
84 :
85 : /**
86 : * The reserve we are being deposited into.
87 : * Set as a trait once we know the reserve.
88 : */
89 : struct TALER_ReservePublicKeyP reserve_pub;
90 :
91 : /**
92 : * PurseDeposit handle while operation is running.
93 : */
94 : struct TALER_EXCHANGE_PurseDepositHandle *dh;
95 :
96 : /**
97 : * Interpreter state.
98 : */
99 : struct TALER_TESTING_Interpreter *is;
100 :
101 : /**
102 : * Reference to the command that established the purse.
103 : */
104 : const char *purse_ref;
105 :
106 : /**
107 : * Reserve history entry that corresponds to this operation.
108 : * Will be of type #TALER_EXCHANGE_RTT_MERGE.
109 : * Only valid if @e purse_complete is true.
110 : */
111 : struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history;
112 : /**
113 : * Expected HTTP response code.
114 : */
115 : unsigned int expected_response_code;
116 :
117 : /**
118 : * Length of the @e coin_references array.
119 : */
120 : unsigned int num_coin_references;
121 :
122 : /**
123 : * Minimum age to apply to all deposits.
124 : */
125 : uint8_t min_age;
126 :
127 : /**
128 : * Set to true if this deposit filled the purse.
129 : */
130 : bool purse_complete;
131 : };
132 :
133 :
134 : /**
135 : * Callback to analyze the /purses/$PID/deposit response, just used to check if
136 : * the response code is acceptable.
137 : *
138 : * @param cls closure.
139 : * @param dr deposit response details
140 : */
141 : static void
142 7 : deposit_cb (void *cls,
143 : const struct TALER_EXCHANGE_PurseDepositResponse *dr)
144 : {
145 7 : struct PurseDepositState *ds = cls;
146 :
147 7 : ds->dh = NULL;
148 7 : if (ds->expected_response_code != dr->hr.http_status)
149 : {
150 0 : TALER_TESTING_unexpected_status (ds->is,
151 : dr->hr.http_status,
152 : ds->expected_response_code);
153 0 : return;
154 : }
155 7 : if (MHD_HTTP_OK == dr->hr.http_status)
156 : {
157 5 : if (-1 !=
158 5 : TALER_amount_cmp (&dr->details.ok.total_deposited,
159 : &dr->details.ok.purse_value_after_fees))
160 : {
161 : const struct TALER_TESTING_Command *purse_cmd;
162 : const struct TALER_ReserveSignatureP *reserve_sig;
163 : const struct TALER_ReservePublicKeyP *reserve_pub;
164 : const struct GNUNET_TIME_Timestamp *merge_timestamp;
165 : const struct TALER_PurseMergePublicKeyP *merge_pub;
166 :
167 5 : purse_cmd = TALER_TESTING_interpreter_lookup_command (ds->is,
168 : ds->purse_ref);
169 5 : GNUNET_assert (NULL != purse_cmd);
170 5 : if (GNUNET_OK !=
171 5 : TALER_TESTING_get_trait_reserve_sig (purse_cmd,
172 : &reserve_sig))
173 : {
174 0 : GNUNET_break (0);
175 0 : TALER_TESTING_interpreter_fail (ds->is);
176 0 : return;
177 : }
178 5 : if (GNUNET_OK !=
179 5 : TALER_TESTING_get_trait_reserve_pub (purse_cmd,
180 : &reserve_pub))
181 : {
182 0 : GNUNET_break (0);
183 0 : TALER_TESTING_interpreter_fail (ds->is);
184 0 : return;
185 : }
186 5 : if (GNUNET_OK !=
187 5 : TALER_TESTING_get_trait_merge_pub (purse_cmd,
188 : &merge_pub))
189 : {
190 0 : GNUNET_break (0);
191 0 : TALER_TESTING_interpreter_fail (ds->is);
192 0 : return;
193 : }
194 5 : ds->reserve_pub = *reserve_pub;
195 5 : if (GNUNET_OK !=
196 5 : TALER_TESTING_get_trait_timestamp (purse_cmd,
197 : 0,
198 : &merge_timestamp))
199 : {
200 0 : GNUNET_break (0);
201 0 : TALER_TESTING_interpreter_fail (ds->is);
202 0 : return;
203 : }
204 :
205 : /* Deposits complete, create trait! */
206 5 : ds->reserve_history.type = TALER_EXCHANGE_RTT_MERGE;
207 : {
208 : struct TALER_EXCHANGE_Keys *keys;
209 : const struct TALER_EXCHANGE_GlobalFee *gf;
210 :
211 5 : keys = TALER_TESTING_get_keys (ds->is);
212 5 : GNUNET_assert (NULL != keys);
213 5 : gf = TALER_EXCHANGE_get_global_fee (keys,
214 : *merge_timestamp);
215 5 : GNUNET_assert (NULL != gf);
216 :
217 : /* Note: change when flags below changes! */
218 : ds->reserve_history.amount
219 5 : = dr->details.ok.purse_value_after_fees;
220 : if (true)
221 : {
222 5 : ds->reserve_history.details.merge_details.purse_fee = gf->fees.purse;
223 : }
224 : else
225 : {
226 : TALER_amount_set_zero (
227 : ds->reserve_history.amount.currency,
228 : &ds->reserve_history.details.merge_details.purse_fee);
229 : }
230 : }
231 : ds->reserve_history.details.merge_details.h_contract_terms
232 5 : = dr->details.ok.h_contract_terms;
233 : ds->reserve_history.details.merge_details.merge_pub
234 5 : = *merge_pub;
235 : ds->reserve_history.details.merge_details.purse_pub
236 5 : = ds->purse_pub;
237 : ds->reserve_history.details.merge_details.reserve_sig
238 5 : = *reserve_sig;
239 : ds->reserve_history.details.merge_details.merge_timestamp
240 5 : = *merge_timestamp;
241 : ds->reserve_history.details.merge_details.purse_expiration
242 5 : = dr->details.ok.purse_expiration;
243 : ds->reserve_history.details.merge_details.min_age
244 5 : = ds->min_age;
245 : ds->reserve_history.details.merge_details.flags
246 5 : = TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE;
247 5 : ds->purse_complete = true;
248 : }
249 : }
250 7 : TALER_TESTING_interpreter_next (ds->is);
251 : }
252 :
253 :
254 : /**
255 : * Run the command.
256 : *
257 : * @param cls closure.
258 : * @param cmd the command to execute.
259 : * @param is the interpreter state.
260 : */
261 : static void
262 7 : deposit_run (void *cls,
263 : const struct TALER_TESTING_Command *cmd,
264 : struct TALER_TESTING_Interpreter *is)
265 7 : {
266 7 : struct PurseDepositState *ds = cls;
267 7 : struct TALER_EXCHANGE_PurseDeposit deposits[ds->num_coin_references];
268 : const struct TALER_PurseContractPublicKeyP *purse_pub;
269 : const struct TALER_TESTING_Command *purse_cmd;
270 :
271 : (void) cmd;
272 7 : ds->is = is;
273 7 : purse_cmd = TALER_TESTING_interpreter_lookup_command (is,
274 : ds->purse_ref);
275 7 : GNUNET_assert (NULL != purse_cmd);
276 7 : if (GNUNET_OK !=
277 7 : TALER_TESTING_get_trait_purse_pub (purse_cmd,
278 : &purse_pub))
279 : {
280 0 : GNUNET_break (0);
281 0 : TALER_TESTING_interpreter_fail (is);
282 0 : return;
283 : }
284 7 : ds->purse_pub = *purse_pub;
285 14 : for (unsigned int i = 0; i<ds->num_coin_references; i++)
286 : {
287 7 : struct Coin *cr = &ds->coin_references[i];
288 7 : struct TALER_EXCHANGE_PurseDeposit *pd = &deposits[i];
289 : const struct TALER_TESTING_Command *coin_cmd;
290 : const struct TALER_CoinSpendPrivateKeyP *coin_priv;
291 7 : const struct TALER_AgeCommitmentProof *age_commitment_proof = NULL;
292 : const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
293 : const struct TALER_DenominationSignature *denom_pub_sig;
294 :
295 7 : coin_cmd = TALER_TESTING_interpreter_lookup_command (is,
296 7 : cr->command_ref);
297 7 : GNUNET_assert (NULL != coin_cmd);
298 7 : if ( (GNUNET_OK !=
299 7 : TALER_TESTING_get_trait_coin_priv (coin_cmd,
300 : cr->coin_index,
301 7 : &coin_priv)) ||
302 : (GNUNET_OK !=
303 7 : TALER_TESTING_get_trait_age_commitment_proof (coin_cmd,
304 : cr->coin_index,
305 : &age_commitment_proof))
306 7 : ||
307 : (GNUNET_OK !=
308 7 : TALER_TESTING_get_trait_denom_pub (coin_cmd,
309 : cr->coin_index,
310 7 : &denom_pub)) ||
311 : (GNUNET_OK !=
312 7 : TALER_TESTING_get_trait_denom_sig (coin_cmd,
313 : cr->coin_index,
314 : &denom_pub_sig)) )
315 : {
316 0 : GNUNET_break (0);
317 0 : TALER_TESTING_interpreter_fail (is);
318 0 : return;
319 : }
320 7 : GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
321 : &cr->coin_pub.eddsa_pub);
322 7 : cr->che.type = TALER_EXCHANGE_CTT_PURSE_DEPOSIT;
323 7 : cr->che.amount = cr->deposit_with_fee;
324 7 : cr->che.details.purse_deposit.purse_pub = *purse_pub;
325 : cr->che.details.purse_deposit.exchange_base_url
326 7 : = TALER_TESTING_get_exchange_url (is);
327 7 : TALER_age_commitment_hash (
328 7 : &age_commitment_proof->commitment,
329 : &cr->che.details.purse_deposit.phac);
330 7 : pd->age_commitment_proof = age_commitment_proof;
331 7 : pd->denom_sig = *denom_pub_sig;
332 7 : pd->coin_priv = *coin_priv;
333 7 : pd->amount = cr->deposit_with_fee;
334 7 : pd->h_denom_pub = denom_pub->h_key;
335 : }
336 :
337 7 : ds->dh = TALER_EXCHANGE_purse_deposit (
338 : TALER_TESTING_interpreter_get_context (is),
339 : TALER_TESTING_get_exchange_url (is),
340 : TALER_TESTING_get_keys (is),
341 : NULL, /* FIXME #7271: WADs support: purse exchange URL */
342 7 : &ds->purse_pub,
343 7 : ds->min_age,
344 : ds->num_coin_references,
345 : deposits,
346 : &deposit_cb,
347 : ds);
348 7 : if (NULL == ds->dh)
349 : {
350 0 : GNUNET_break (0);
351 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
352 : "Could not deposit into purse\n");
353 0 : TALER_TESTING_interpreter_fail (is);
354 0 : return;
355 : }
356 : }
357 :
358 :
359 : /**
360 : * Free the state of a "deposit" CMD, and possibly cancel a
361 : * pending operation thereof.
362 : *
363 : * @param cls closure, must be a `struct PurseDepositState`.
364 : * @param cmd the command which is being cleaned up.
365 : */
366 : static void
367 7 : deposit_cleanup (void *cls,
368 : const struct TALER_TESTING_Command *cmd)
369 : {
370 7 : struct PurseDepositState *ds = cls;
371 :
372 7 : if (NULL != ds->dh)
373 : {
374 0 : TALER_TESTING_command_incomplete (ds->is,
375 : cmd->label);
376 0 : TALER_EXCHANGE_purse_deposit_cancel (ds->dh);
377 0 : ds->dh = NULL;
378 : }
379 14 : for (unsigned int i = 0; i<ds->num_coin_references; i++)
380 7 : GNUNET_free (ds->coin_references[i].command_ref);
381 7 : GNUNET_free (ds->coin_references);
382 7 : GNUNET_free (ds);
383 7 : }
384 :
385 :
386 : /**
387 : * Offer internal data from a "deposit" CMD, to other commands.
388 : *
389 : * @param cls closure.
390 : * @param[out] ret result.
391 : * @param trait name of the trait.
392 : * @param index index number of the object to offer.
393 : * @return #GNUNET_OK on success.
394 : */
395 : static enum GNUNET_GenericReturnValue
396 9 : deposit_traits (void *cls,
397 : const void **ret,
398 : const char *trait,
399 : unsigned int index)
400 : {
401 9 : struct PurseDepositState *ds = cls;
402 :
403 9 : if (index >= ds->num_coin_references)
404 1 : return GNUNET_NO;
405 : {
406 8 : const struct Coin *co = &ds->coin_references[index];
407 : struct TALER_TESTING_Trait traits[] = {
408 : /* history entry MUST be first due to response code logic below! */
409 8 : TALER_TESTING_make_trait_reserve_history (0,
410 8 : &ds->reserve_history),
411 8 : TALER_TESTING_make_trait_coin_history (index,
412 : &co->che),
413 8 : TALER_TESTING_make_trait_coin_pub (index,
414 : &co->coin_pub),
415 8 : TALER_TESTING_make_trait_reserve_pub (&ds->reserve_pub),
416 8 : TALER_TESTING_make_trait_purse_pub (&ds->purse_pub),
417 8 : TALER_TESTING_trait_end ()
418 : };
419 :
420 8 : return TALER_TESTING_get_trait (ds->purse_complete
421 : ? &traits[0] /* we have reserve history */
422 : : &traits[1], /* skip reserve history */
423 : ret,
424 : trait,
425 : index);
426 : }
427 : }
428 :
429 :
430 : struct TALER_TESTING_Command
431 7 : TALER_TESTING_cmd_purse_deposit_coins (
432 : const char *label,
433 : unsigned int expected_http_status,
434 : uint8_t min_age,
435 : const char *purse_ref,
436 : ...)
437 : {
438 : struct PurseDepositState *ds;
439 :
440 7 : ds = GNUNET_new (struct PurseDepositState);
441 7 : ds->expected_response_code = expected_http_status;
442 7 : ds->min_age = min_age;
443 7 : ds->purse_ref = purse_ref;
444 : {
445 : va_list ap;
446 : unsigned int i;
447 : const char *ref;
448 : const char *val;
449 :
450 7 : va_start (ap, purse_ref);
451 21 : while (NULL != (va_arg (ap, const char *)))
452 14 : ds->num_coin_references++;
453 7 : va_end (ap);
454 7 : GNUNET_assert (0 == (ds->num_coin_references % 2));
455 7 : ds->num_coin_references /= 2;
456 7 : ds->coin_references = GNUNET_new_array (ds->num_coin_references,
457 : struct Coin);
458 7 : i = 0;
459 7 : va_start (ap, purse_ref);
460 14 : while (NULL != (ref = va_arg (ap, const char *)))
461 : {
462 7 : struct Coin *c = &ds->coin_references[i++];
463 :
464 7 : GNUNET_assert (NULL != (val = va_arg (ap,
465 : const char *)));
466 7 : GNUNET_assert (GNUNET_OK ==
467 : TALER_TESTING_parse_coin_reference (
468 : ref,
469 : &c->command_ref,
470 : &c->coin_index));
471 7 : GNUNET_assert (GNUNET_OK ==
472 : TALER_string_to_amount (val,
473 : &c->deposit_with_fee));
474 : }
475 7 : va_end (ap);
476 : }
477 : {
478 7 : struct TALER_TESTING_Command cmd = {
479 : .cls = ds,
480 : .label = label,
481 : .run = &deposit_run,
482 : .cleanup = &deposit_cleanup,
483 : .traits = &deposit_traits
484 : };
485 :
486 7 : return cmd;
487 : }
488 : }
489 :
490 :
491 : /* end of testing_api_cmd_purse_deposit.c */
|