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/platform.h"
25 : #include "taler/taler_json_lib.h"
26 : #include <gnunet/gnunet_curl_lib.h>
27 : #include "taler/taler_testing_lib.h"
28 : #include "taler/taler_signatures.h"
29 : #include "taler/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_PostPursesDepositHandle *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_PostPursesDepositResponse *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_post_purses_deposit_create (
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 7 : if (NULL == ds->dh)
347 : {
348 0 : GNUNET_break (0);
349 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
350 : "Could not deposit into purse\n");
351 0 : TALER_TESTING_interpreter_fail (is);
352 0 : return;
353 : }
354 7 : GNUNET_assert (TALER_EC_NONE ==
355 : TALER_EXCHANGE_post_purses_deposit_start (ds->dh,
356 : &deposit_cb,
357 : ds));
358 : }
359 :
360 :
361 : /**
362 : * Free the state of a "deposit" CMD, and possibly cancel a
363 : * pending operation thereof.
364 : *
365 : * @param cls closure, must be a `struct PurseDepositState`.
366 : * @param cmd the command which is being cleaned up.
367 : */
368 : static void
369 7 : deposit_cleanup (void *cls,
370 : const struct TALER_TESTING_Command *cmd)
371 : {
372 7 : struct PurseDepositState *ds = cls;
373 :
374 7 : if (NULL != ds->dh)
375 : {
376 0 : TALER_TESTING_command_incomplete (ds->is,
377 : cmd->label);
378 0 : TALER_EXCHANGE_post_purses_deposit_cancel (ds->dh);
379 0 : ds->dh = NULL;
380 : }
381 14 : for (unsigned int i = 0; i<ds->num_coin_references; i++)
382 7 : GNUNET_free (ds->coin_references[i].command_ref);
383 7 : GNUNET_free (ds->coin_references);
384 7 : GNUNET_free (ds);
385 7 : }
386 :
387 :
388 : /**
389 : * Offer internal data from a "deposit" CMD, to other commands.
390 : *
391 : * @param cls closure.
392 : * @param[out] ret result.
393 : * @param trait name of the trait.
394 : * @param index index number of the object to offer.
395 : * @return #GNUNET_OK on success.
396 : */
397 : static enum GNUNET_GenericReturnValue
398 9 : deposit_traits (void *cls,
399 : const void **ret,
400 : const char *trait,
401 : unsigned int index)
402 : {
403 9 : struct PurseDepositState *ds = cls;
404 :
405 9 : if (index >= ds->num_coin_references)
406 1 : return GNUNET_NO;
407 : {
408 8 : const struct Coin *co = &ds->coin_references[index];
409 : struct TALER_TESTING_Trait traits[] = {
410 : /* history entry MUST be first due to response code logic below! */
411 8 : TALER_TESTING_make_trait_reserve_history (0,
412 8 : &ds->reserve_history),
413 8 : TALER_TESTING_make_trait_coin_history (index,
414 : &co->che),
415 8 : TALER_TESTING_make_trait_coin_pub (index,
416 : &co->coin_pub),
417 8 : TALER_TESTING_make_trait_reserve_pub (&ds->reserve_pub),
418 8 : TALER_TESTING_make_trait_purse_pub (&ds->purse_pub),
419 8 : TALER_TESTING_trait_end ()
420 : };
421 :
422 8 : return TALER_TESTING_get_trait (ds->purse_complete
423 : ? &traits[0] /* we have reserve history */
424 : : &traits[1], /* skip reserve history */
425 : ret,
426 : trait,
427 : index);
428 : }
429 : }
430 :
431 :
432 : struct TALER_TESTING_Command
433 7 : TALER_TESTING_cmd_purse_deposit_coins (
434 : const char *label,
435 : unsigned int expected_http_status,
436 : uint8_t min_age,
437 : const char *purse_ref,
438 : ...)
439 : {
440 : struct PurseDepositState *ds;
441 :
442 7 : ds = GNUNET_new (struct PurseDepositState);
443 7 : ds->expected_response_code = expected_http_status;
444 7 : ds->min_age = min_age;
445 7 : ds->purse_ref = purse_ref;
446 : {
447 : va_list ap;
448 : unsigned int i;
449 : const char *ref;
450 : const char *val;
451 :
452 7 : va_start (ap, purse_ref);
453 21 : while (NULL != (va_arg (ap, const char *)))
454 14 : ds->num_coin_references++;
455 7 : va_end (ap);
456 7 : GNUNET_assert (0 == (ds->num_coin_references % 2));
457 7 : ds->num_coin_references /= 2;
458 7 : ds->coin_references = GNUNET_new_array (ds->num_coin_references,
459 : struct Coin);
460 7 : i = 0;
461 7 : va_start (ap, purse_ref);
462 14 : while (NULL != (ref = va_arg (ap, const char *)))
463 : {
464 7 : struct Coin *c = &ds->coin_references[i++];
465 :
466 7 : GNUNET_assert (NULL != (val = va_arg (ap,
467 : const char *)));
468 7 : GNUNET_assert (GNUNET_OK ==
469 : TALER_TESTING_parse_coin_reference (
470 : ref,
471 : &c->command_ref,
472 : &c->coin_index));
473 7 : GNUNET_assert (GNUNET_OK ==
474 : TALER_string_to_amount (val,
475 : &c->deposit_with_fee));
476 : }
477 7 : va_end (ap);
478 : }
479 : {
480 7 : struct TALER_TESTING_Command cmd = {
481 : .cls = ds,
482 : .label = label,
483 : .run = &deposit_run,
484 : .cleanup = &deposit_cleanup,
485 : .traits = &deposit_traits
486 : };
487 :
488 7 : return cmd;
489 : }
490 : }
491 :
492 :
493 : /* end of testing_api_cmd_purse_deposit.c */
|