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