Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2022 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_create_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 : * index of the specific coin in the traits of @e command_ref.
43 : */
44 : unsigned int coin_index;
45 :
46 : /**
47 : * Public key of the deposited coin.
48 : */
49 : struct TALER_CoinSpendPublicKeyP coin_pub;
50 :
51 : /**
52 : * Amount to deposit (with fee).
53 : */
54 : struct TALER_Amount deposit_with_fee;
55 :
56 : /**
57 : * Entry in the coin's history generated by this operation.
58 : */
59 : struct TALER_EXCHANGE_CoinHistoryEntry che;
60 :
61 : };
62 :
63 :
64 : /**
65 : * State for a "purse create deposit" CMD.
66 : */
67 : struct PurseCreateDepositState
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 : * JSON string describing what a proposal is about.
82 : */
83 : json_t *contract_terms;
84 :
85 : /**
86 : * Purse expiration time.
87 : */
88 : struct GNUNET_TIME_Timestamp purse_expiration;
89 :
90 : /**
91 : * Relative purse expiration time.
92 : */
93 : struct GNUNET_TIME_Relative rel_expiration;
94 :
95 : /**
96 : * Set (by the interpreter) to a fresh private key. This
97 : * key will be used to create the purse.
98 : */
99 : struct TALER_PurseContractPrivateKeyP purse_priv;
100 :
101 : /**
102 : * Set (by the interpreter) to a fresh private key. This
103 : * key will be used to merge the purse.
104 : */
105 : struct TALER_PurseMergePrivateKeyP merge_priv;
106 :
107 : /**
108 : * Set (by the interpreter) to a fresh private key. This
109 : * key will be used to decrypt the contract.
110 : */
111 : struct TALER_ContractDiffiePrivateP contract_priv;
112 :
113 : /**
114 : * Signing key used by the exchange to sign the
115 : * deposit confirmation.
116 : */
117 : struct TALER_ExchangePublicKeyP exchange_pub;
118 :
119 : /**
120 : * Signature from the exchange on the
121 : * deposit confirmation.
122 : */
123 : struct TALER_ExchangeSignatureP exchange_sig;
124 :
125 : /**
126 : * Set (by the interpreter) to a public key corresponding
127 : * to @e purse_priv.
128 : */
129 : struct TALER_PurseContractPublicKeyP purse_pub;
130 :
131 : /**
132 : * PurseCreateDeposit handle while operation is running.
133 : */
134 : struct TALER_EXCHANGE_PurseCreateDepositHandle *dh;
135 :
136 : /**
137 : * Interpreter state.
138 : */
139 : struct TALER_TESTING_Interpreter *is;
140 :
141 : /**
142 : * Expected HTTP response code.
143 : */
144 : unsigned int expected_response_code;
145 :
146 : /**
147 : * Length of the @e coin_references array.
148 : */
149 : unsigned int num_coin_references;
150 :
151 : /**
152 : * Should we upload the contract?
153 : */
154 : bool upload_contract;
155 :
156 : };
157 :
158 :
159 : /**
160 : * Callback to analyze the /purses/$PID/create response, just used to check if
161 : * the response code is acceptable.
162 : *
163 : * @param cls closure.
164 : * @param dr deposit response details
165 : */
166 : static void
167 11 : deposit_cb (void *cls,
168 : const struct TALER_EXCHANGE_PurseCreateDepositResponse *dr)
169 : {
170 11 : struct PurseCreateDepositState *ds = cls;
171 :
172 11 : ds->dh = NULL;
173 11 : if (ds->expected_response_code != dr->hr.http_status)
174 : {
175 0 : TALER_TESTING_unexpected_status (ds->is,
176 : dr->hr.http_status,
177 : ds->expected_response_code);
178 0 : return;
179 : }
180 11 : if (MHD_HTTP_OK == dr->hr.http_status)
181 : {
182 9 : ds->exchange_pub = dr->details.ok.exchange_pub;
183 9 : ds->exchange_sig = dr->details.ok.exchange_sig;
184 : }
185 11 : TALER_TESTING_interpreter_next (ds->is);
186 : }
187 :
188 :
189 : /**
190 : * Run the command.
191 : *
192 : * @param cls closure.
193 : * @param cmd the command to execute.
194 : * @param is the interpreter state.
195 : */
196 : static void
197 11 : deposit_run (void *cls,
198 : const struct TALER_TESTING_Command *cmd,
199 : struct TALER_TESTING_Interpreter *is)
200 11 : {
201 11 : struct PurseCreateDepositState *ds = cls;
202 11 : struct TALER_EXCHANGE_PurseDeposit deposits[ds->num_coin_references];
203 :
204 : (void) cmd;
205 11 : ds->is = is;
206 11 : GNUNET_CRYPTO_eddsa_key_create (&ds->purse_priv.eddsa_priv);
207 11 : GNUNET_CRYPTO_eddsa_key_create (&ds->merge_priv.eddsa_priv);
208 11 : GNUNET_CRYPTO_ecdhe_key_create (&ds->contract_priv.ecdhe_priv);
209 11 : GNUNET_CRYPTO_eddsa_key_get_public (&ds->purse_priv.eddsa_priv,
210 : &ds->purse_pub.eddsa_pub);
211 :
212 22 : for (unsigned int i = 0; i<ds->num_coin_references; i++)
213 : {
214 11 : struct Coin *cr = &ds->coin_references[i];
215 11 : struct TALER_EXCHANGE_PurseDeposit *pd = &deposits[i];
216 : const struct TALER_TESTING_Command *coin_cmd;
217 : const struct TALER_CoinSpendPrivateKeyP *coin_priv;
218 11 : const struct TALER_AgeCommitmentProof *age_commitment_proof = NULL;
219 : const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
220 : const struct TALER_DenominationSignature *denom_pub_sig;
221 :
222 11 : coin_cmd = TALER_TESTING_interpreter_lookup_command (is,
223 11 : cr->command_ref);
224 11 : if (NULL == coin_cmd)
225 : {
226 0 : GNUNET_break (0);
227 0 : TALER_TESTING_interpreter_fail (is);
228 0 : return;
229 : }
230 :
231 11 : if ( (GNUNET_OK !=
232 11 : TALER_TESTING_get_trait_coin_priv (coin_cmd,
233 : cr->coin_index,
234 11 : &coin_priv)) ||
235 : (GNUNET_OK !=
236 11 : TALER_TESTING_get_trait_age_commitment_proof (coin_cmd,
237 : cr->coin_index,
238 : &age_commitment_proof))
239 11 : ||
240 : (GNUNET_OK !=
241 11 : TALER_TESTING_get_trait_denom_pub (coin_cmd,
242 : cr->coin_index,
243 11 : &denom_pub)) ||
244 : (GNUNET_OK !=
245 11 : TALER_TESTING_get_trait_denom_sig (coin_cmd,
246 : cr->coin_index,
247 : &denom_pub_sig)) )
248 : {
249 0 : GNUNET_break (0);
250 0 : TALER_TESTING_interpreter_fail (is);
251 0 : return;
252 : }
253 11 : pd->age_commitment_proof = age_commitment_proof;
254 11 : pd->denom_sig = *denom_pub_sig;
255 11 : pd->coin_priv = *coin_priv;
256 11 : pd->amount = cr->deposit_with_fee;
257 11 : pd->h_denom_pub = denom_pub->h_key;
258 11 : GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
259 : &cr->coin_pub.eddsa_pub);
260 11 : cr->che.type = TALER_EXCHANGE_CTT_PURSE_DEPOSIT;
261 11 : cr->che.amount = cr->deposit_with_fee;
262 11 : GNUNET_CRYPTO_eddsa_key_get_public (
263 11 : &ds->purse_priv.eddsa_priv,
264 : &cr->che.details.purse_deposit.purse_pub.eddsa_pub);
265 : cr->che.details.purse_deposit.exchange_base_url
266 11 : = TALER_TESTING_get_exchange_url (is);
267 11 : TALER_age_commitment_hash (
268 11 : &age_commitment_proof->commitment,
269 : &cr->che.details.purse_deposit.phac);
270 : }
271 :
272 : ds->purse_expiration =
273 11 : GNUNET_TIME_absolute_to_timestamp (
274 : GNUNET_TIME_relative_to_absolute (ds->rel_expiration));
275 11 : GNUNET_assert (0 ==
276 : json_object_set_new (
277 : ds->contract_terms,
278 : "pay_deadline",
279 : GNUNET_JSON_from_timestamp (ds->purse_expiration)));
280 11 : ds->dh = TALER_EXCHANGE_purse_create_with_deposit (
281 : TALER_TESTING_interpreter_get_context (is),
282 : TALER_TESTING_get_exchange_url (is),
283 : TALER_TESTING_get_keys (is),
284 11 : &ds->purse_priv,
285 11 : &ds->merge_priv,
286 11 : &ds->contract_priv,
287 11 : ds->contract_terms,
288 : ds->num_coin_references,
289 : deposits,
290 11 : ds->upload_contract,
291 : &deposit_cb,
292 : ds);
293 11 : if (NULL == ds->dh)
294 : {
295 0 : GNUNET_break (0);
296 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
297 : "Could not create purse with deposit\n");
298 0 : TALER_TESTING_interpreter_fail (is);
299 0 : return;
300 : }
301 : }
302 :
303 :
304 : /**
305 : * Free the state of a "deposit" CMD, and possibly cancel a
306 : * pending operation thereof.
307 : *
308 : * @param cls closure, must be a `struct PurseCreateDepositState`.
309 : * @param cmd the command which is being cleaned up.
310 : */
311 : static void
312 11 : deposit_cleanup (void *cls,
313 : const struct TALER_TESTING_Command *cmd)
314 : {
315 11 : struct PurseCreateDepositState *ds = cls;
316 :
317 11 : if (NULL != ds->dh)
318 : {
319 0 : TALER_TESTING_command_incomplete (ds->is,
320 : cmd->label);
321 0 : TALER_EXCHANGE_purse_create_with_deposit_cancel (ds->dh);
322 0 : ds->dh = NULL;
323 : }
324 22 : for (unsigned int i = 0; i<ds->num_coin_references; i++)
325 11 : GNUNET_free (ds->coin_references[i].command_ref);
326 11 : json_decref (ds->contract_terms);
327 11 : GNUNET_free (ds->coin_references);
328 11 : GNUNET_free (ds);
329 11 : }
330 :
331 :
332 : /**
333 : * Offer internal data from a "deposit" CMD, to other commands.
334 : *
335 : * @param cls closure.
336 : * @param[out] ret result.
337 : * @param trait name of the trait.
338 : * @param index index number of the object to offer.
339 : * @return #GNUNET_OK on success.
340 : */
341 : static enum GNUNET_GenericReturnValue
342 32 : deposit_traits (void *cls,
343 : const void **ret,
344 : const char *trait,
345 : unsigned int index)
346 : {
347 32 : struct PurseCreateDepositState *ds = cls;
348 32 : if (index >= ds->num_coin_references)
349 2 : return GNUNET_NO;
350 :
351 : {
352 30 : const struct Coin *co = &ds->coin_references[index];
353 : struct TALER_TESTING_Trait traits[] = {
354 30 : TALER_TESTING_make_trait_merge_priv (&ds->merge_priv),
355 30 : TALER_TESTING_make_trait_contract_priv (&ds->contract_priv),
356 30 : TALER_TESTING_make_trait_coin_history (index,
357 : &co->che),
358 30 : TALER_TESTING_make_trait_coin_pub (index,
359 : &co->coin_pub),
360 30 : TALER_TESTING_make_trait_purse_priv (&ds->purse_priv),
361 30 : TALER_TESTING_make_trait_purse_pub (&ds->purse_pub),
362 30 : TALER_TESTING_make_trait_contract_terms (ds->contract_terms),
363 30 : TALER_TESTING_make_trait_deposit_amount (0,
364 30 : &ds->target_amount),
365 30 : TALER_TESTING_make_trait_timestamp (index,
366 30 : &ds->purse_expiration),
367 30 : TALER_TESTING_trait_end ()
368 : };
369 :
370 30 : return TALER_TESTING_get_trait (traits,
371 : ret,
372 : trait,
373 : index);
374 : }
375 : }
376 :
377 :
378 : struct TALER_TESTING_Command
379 11 : TALER_TESTING_cmd_purse_create_with_deposit (
380 : const char *label,
381 : unsigned int expected_http_status,
382 : const char *contract_terms,
383 : bool upload_contract,
384 : struct GNUNET_TIME_Relative purse_expiration,
385 : ...)
386 : {
387 : struct PurseCreateDepositState *ds;
388 :
389 11 : ds = GNUNET_new (struct PurseCreateDepositState);
390 11 : ds->rel_expiration = purse_expiration;
391 11 : ds->upload_contract = upload_contract;
392 11 : ds->expected_response_code = expected_http_status;
393 11 : ds->contract_terms = json_loads (contract_terms,
394 : JSON_REJECT_DUPLICATES,
395 : NULL);
396 11 : if (NULL == ds->contract_terms)
397 : {
398 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
399 : "Failed to parse contract terms `%s' for CMD `%s'\n",
400 : contract_terms,
401 : label);
402 0 : GNUNET_assert (0);
403 : }
404 : {
405 : va_list ap;
406 : unsigned int i;
407 : const char *ref;
408 : const char *val;
409 :
410 11 : va_start (ap, purse_expiration);
411 33 : while (NULL != (va_arg (ap, const char *)))
412 22 : ds->num_coin_references++;
413 11 : va_end (ap);
414 11 : GNUNET_assert (0 == (ds->num_coin_references % 2));
415 11 : ds->num_coin_references /= 2;
416 11 : ds->coin_references = GNUNET_new_array (ds->num_coin_references,
417 : struct Coin);
418 11 : i = 0;
419 11 : va_start (ap, purse_expiration);
420 22 : while (NULL != (ref = va_arg (ap, const char *)))
421 : {
422 11 : struct Coin *c = &ds->coin_references[i++];
423 :
424 11 : GNUNET_assert (NULL != (val = va_arg (ap, const char *)));
425 11 : GNUNET_assert (GNUNET_OK ==
426 : TALER_TESTING_parse_coin_reference (
427 : ref,
428 : &c->command_ref,
429 : &c->coin_index));
430 11 : GNUNET_assert (GNUNET_OK ==
431 : TALER_string_to_amount (val,
432 : &c->deposit_with_fee));
433 : }
434 11 : va_end (ap);
435 : }
436 : {
437 11 : struct TALER_TESTING_Command cmd = {
438 : .cls = ds,
439 : .label = label,
440 : .run = &deposit_run,
441 : .cleanup = &deposit_cleanup,
442 : .traits = &deposit_traits
443 : };
444 :
445 11 : return cmd;
446 : }
447 : }
448 :
449 :
450 : /* end of testing_api_cmd_purse_create_deposit.c */
|