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 "taler/taler_json_lib.h"
25 : #include <gnunet/gnunet_curl_lib.h>
26 : #include "taler/taler_testing_lib.h"
27 : #include "taler/taler_signatures.h"
28 :
29 : /**
30 : * Information we keep per deposited coin.
31 : */
32 : struct Coin
33 : {
34 : /**
35 : * Reference to the respective command.
36 : */
37 : char *command_ref;
38 :
39 : /**
40 : * Entry in the coin's history generated by this operation.
41 : */
42 : struct TALER_EXCHANGE_CoinHistoryEntry che;
43 :
44 : /**
45 : * Public key of the deposited coin.
46 : */
47 : struct TALER_CoinSpendPublicKeyP coin_pub;
48 :
49 : /**
50 : * index of the specific coin in the traits of @e command_ref.
51 : */
52 : unsigned int coin_index;
53 :
54 : /**
55 : * Amount to deposit (with fee).
56 : */
57 : struct TALER_Amount deposit_with_fee;
58 :
59 : };
60 :
61 :
62 : /**
63 : * State for a "purse deposit" CMD.
64 : */
65 : struct PurseDepositState
66 : {
67 :
68 : /**
69 : * Total purse target amount without fees.
70 : */
71 : struct TALER_Amount target_amount;
72 :
73 : /**
74 : * Reference to any command that is able to provide a coin.
75 : */
76 : struct Coin *coin_references;
77 :
78 : /**
79 : * The purse's public key.
80 : */
81 : struct TALER_PurseContractPublicKeyP purse_pub;
82 :
83 : /**
84 : * The reserve we are being deposited into.
85 : * Set as a trait once we know the reserve.
86 : */
87 : struct TALER_ReservePublicKeyP reserve_pub;
88 :
89 : /**
90 : * PurseDeposit handle while operation is running.
91 : */
92 : struct TALER_EXCHANGE_PostPursesDepositHandle *dh;
93 :
94 : /**
95 : * Interpreter state.
96 : */
97 : struct TALER_TESTING_Interpreter *is;
98 :
99 : /**
100 : * Reference to the command that established the purse.
101 : */
102 : const char *purse_ref;
103 :
104 : /**
105 : * Reserve history entry that corresponds to this operation.
106 : * Will be of type #TALER_EXCHANGE_RTT_MERGE.
107 : * Only valid if @e purse_complete is true.
108 : */
109 : struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history;
110 : /**
111 : * Expected HTTP response code.
112 : */
113 : unsigned int expected_response_code;
114 :
115 : /**
116 : * Length of the @e coin_references array.
117 : */
118 : unsigned int num_coin_references;
119 :
120 : /**
121 : * Minimum age to apply to all deposits.
122 : */
123 : uint8_t min_age;
124 :
125 : /**
126 : * Set to true if this deposit filled the purse.
127 : */
128 : bool purse_complete;
129 : };
130 :
131 :
132 : /**
133 : * Callback to analyze the /purses/$PID/deposit response, just used to check if
134 : * the response code is acceptable.
135 : *
136 : * @param cls closure.
137 : * @param dr deposit response details
138 : */
139 : static void
140 7 : deposit_cb (void *cls,
141 : const struct TALER_EXCHANGE_PostPursesDepositResponse *dr)
142 : {
143 7 : struct PurseDepositState *ds = cls;
144 :
145 7 : ds->dh = NULL;
146 7 : if (ds->expected_response_code != dr->hr.http_status)
147 : {
148 0 : TALER_TESTING_unexpected_status (ds->is,
149 : dr->hr.http_status,
150 : ds->expected_response_code);
151 0 : return;
152 : }
153 7 : if (MHD_HTTP_OK == dr->hr.http_status)
154 : {
155 5 : if (-1 !=
156 5 : TALER_amount_cmp (&dr->details.ok.total_deposited,
157 : &dr->details.ok.purse_value_after_fees))
158 : {
159 : const struct TALER_TESTING_Command *purse_cmd;
160 : const struct TALER_ReserveSignatureP *reserve_sig;
161 : const struct TALER_ReservePublicKeyP *reserve_pub;
162 : const struct GNUNET_TIME_Timestamp *merge_timestamp;
163 : const struct TALER_PurseMergePublicKeyP *merge_pub;
164 :
165 5 : purse_cmd = TALER_TESTING_interpreter_lookup_command (ds->is,
166 : ds->purse_ref);
167 5 : GNUNET_assert (NULL != purse_cmd);
168 5 : if (GNUNET_OK !=
169 5 : TALER_TESTING_get_trait_reserve_sig (purse_cmd,
170 : &reserve_sig))
171 : {
172 0 : GNUNET_break (0);
173 0 : TALER_TESTING_interpreter_fail (ds->is);
174 0 : return;
175 : }
176 5 : if (GNUNET_OK !=
177 5 : TALER_TESTING_get_trait_reserve_pub (purse_cmd,
178 : &reserve_pub))
179 : {
180 0 : GNUNET_break (0);
181 0 : TALER_TESTING_interpreter_fail (ds->is);
182 0 : return;
183 : }
184 5 : if (GNUNET_OK !=
185 5 : TALER_TESTING_get_trait_merge_pub (purse_cmd,
186 : &merge_pub))
187 : {
188 0 : GNUNET_break (0);
189 0 : TALER_TESTING_interpreter_fail (ds->is);
190 0 : return;
191 : }
192 5 : ds->reserve_pub = *reserve_pub;
193 5 : if (GNUNET_OK !=
194 5 : TALER_TESTING_get_trait_timestamp (purse_cmd,
195 : 0,
196 : &merge_timestamp))
197 : {
198 0 : GNUNET_break (0);
199 0 : TALER_TESTING_interpreter_fail (ds->is);
200 0 : return;
201 : }
202 :
203 : /* Deposits complete, create trait! */
204 5 : ds->reserve_history.type = TALER_EXCHANGE_RTT_MERGE;
205 : {
206 : struct TALER_EXCHANGE_Keys *keys;
207 : const struct TALER_EXCHANGE_GlobalFee *gf;
208 :
209 5 : keys = TALER_TESTING_get_keys (ds->is);
210 5 : GNUNET_assert (NULL != keys);
211 5 : gf = TALER_EXCHANGE_get_global_fee (keys,
212 : *merge_timestamp);
213 5 : GNUNET_assert (NULL != gf);
214 :
215 : /* Note: change when flags below changes! */
216 : ds->reserve_history.amount
217 5 : = dr->details.ok.purse_value_after_fees;
218 : if (true)
219 : {
220 5 : ds->reserve_history.details.merge_details.purse_fee = gf->fees.purse;
221 : }
222 : else
223 : {
224 : TALER_amount_set_zero (
225 : ds->reserve_history.amount.currency,
226 : &ds->reserve_history.details.merge_details.purse_fee);
227 : }
228 : }
229 : ds->reserve_history.details.merge_details.h_contract_terms
230 5 : = dr->details.ok.h_contract_terms;
231 : ds->reserve_history.details.merge_details.merge_pub
232 5 : = *merge_pub;
233 : ds->reserve_history.details.merge_details.purse_pub
234 5 : = ds->purse_pub;
235 : ds->reserve_history.details.merge_details.reserve_sig
236 5 : = *reserve_sig;
237 : ds->reserve_history.details.merge_details.merge_timestamp
238 5 : = *merge_timestamp;
239 : ds->reserve_history.details.merge_details.purse_expiration
240 5 : = dr->details.ok.purse_expiration;
241 : ds->reserve_history.details.merge_details.min_age
242 5 : = ds->min_age;
243 : ds->reserve_history.details.merge_details.flags
244 5 : = TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE;
245 5 : ds->purse_complete = true;
246 : }
247 : }
248 7 : TALER_TESTING_interpreter_next (ds->is);
249 : }
250 :
251 :
252 : /**
253 : * Run the command.
254 : *
255 : * @param cls closure.
256 : * @param cmd the command to execute.
257 : * @param is the interpreter state.
258 : */
259 : static void
260 7 : deposit_run (void *cls,
261 : const struct TALER_TESTING_Command *cmd,
262 : struct TALER_TESTING_Interpreter *is)
263 7 : {
264 7 : struct PurseDepositState *ds = cls;
265 7 : struct TALER_EXCHANGE_PurseDeposit deposits[ds->num_coin_references];
266 : const struct TALER_PurseContractPublicKeyP *purse_pub;
267 : const struct TALER_TESTING_Command *purse_cmd;
268 :
269 : (void) cmd;
270 7 : ds->is = is;
271 7 : purse_cmd = TALER_TESTING_interpreter_lookup_command (is,
272 : ds->purse_ref);
273 7 : GNUNET_assert (NULL != purse_cmd);
274 7 : if (GNUNET_OK !=
275 7 : TALER_TESTING_get_trait_purse_pub (purse_cmd,
276 : &purse_pub))
277 : {
278 0 : GNUNET_break (0);
279 0 : TALER_TESTING_interpreter_fail (is);
280 0 : return;
281 : }
282 7 : ds->purse_pub = *purse_pub;
283 14 : for (unsigned int i = 0; i<ds->num_coin_references; i++)
284 : {
285 7 : struct Coin *cr = &ds->coin_references[i];
286 7 : struct TALER_EXCHANGE_PurseDeposit *pd = &deposits[i];
287 : const struct TALER_TESTING_Command *coin_cmd;
288 : const struct TALER_CoinSpendPrivateKeyP *coin_priv;
289 7 : const struct TALER_AgeCommitmentProof *age_commitment_proof = NULL;
290 : const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
291 : const struct TALER_DenominationSignature *denom_pub_sig;
292 :
293 7 : coin_cmd = TALER_TESTING_interpreter_lookup_command (is,
294 7 : cr->command_ref);
295 7 : GNUNET_assert (NULL != coin_cmd);
296 7 : if ( (GNUNET_OK !=
297 7 : TALER_TESTING_get_trait_coin_priv (coin_cmd,
298 : cr->coin_index,
299 7 : &coin_priv)) ||
300 : (GNUNET_OK !=
301 7 : TALER_TESTING_get_trait_age_commitment_proof (coin_cmd,
302 : cr->coin_index,
303 : &age_commitment_proof))
304 7 : ||
305 : (GNUNET_OK !=
306 7 : TALER_TESTING_get_trait_denom_pub (coin_cmd,
307 : cr->coin_index,
308 7 : &denom_pub)) ||
309 : (GNUNET_OK !=
310 7 : TALER_TESTING_get_trait_denom_sig (coin_cmd,
311 : cr->coin_index,
312 : &denom_pub_sig)) )
313 : {
314 0 : GNUNET_break (0);
315 0 : TALER_TESTING_interpreter_fail (is);
316 0 : return;
317 : }
318 7 : GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
319 : &cr->coin_pub.eddsa_pub);
320 7 : cr->che.type = TALER_EXCHANGE_CTT_PURSE_DEPOSIT;
321 7 : cr->che.amount = cr->deposit_with_fee;
322 7 : cr->che.details.purse_deposit.purse_pub = *purse_pub;
323 : cr->che.details.purse_deposit.exchange_base_url
324 7 : = TALER_TESTING_get_exchange_url (is);
325 7 : TALER_age_commitment_hash (
326 7 : &age_commitment_proof->commitment,
327 : &cr->che.details.purse_deposit.phac);
328 7 : pd->age_commitment_proof = age_commitment_proof;
329 7 : pd->denom_sig = *denom_pub_sig;
330 7 : pd->coin_priv = *coin_priv;
331 7 : pd->amount = cr->deposit_with_fee;
332 7 : pd->h_denom_pub = denom_pub->h_key;
333 : }
334 :
335 7 : ds->dh = TALER_EXCHANGE_post_purses_deposit_create (
336 : TALER_TESTING_interpreter_get_context (is),
337 : TALER_TESTING_get_exchange_url (is),
338 : TALER_TESTING_get_keys (is),
339 : NULL, /* FIXME #7271: WADs support: purse exchange URL */
340 7 : &ds->purse_pub,
341 7 : ds->min_age,
342 : ds->num_coin_references,
343 : deposits);
344 7 : if (NULL == ds->dh)
345 : {
346 0 : GNUNET_break (0);
347 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
348 : "Could not deposit into purse\n");
349 0 : TALER_TESTING_interpreter_fail (is);
350 0 : return;
351 : }
352 7 : GNUNET_assert (TALER_EC_NONE ==
353 : TALER_EXCHANGE_post_purses_deposit_start (ds->dh,
354 : &deposit_cb,
355 : ds));
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_post_purses_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 */
|