Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2022, 2024 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_merge.c
21 : * @brief command for testing /purses/$PID/merge
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 : /**
33 : * State for a "purse create deposit" CMD.
34 : */
35 : struct PurseMergeState
36 : {
37 :
38 : /**
39 : * Merge time.
40 : */
41 : struct GNUNET_TIME_Timestamp merge_timestamp;
42 :
43 : /**
44 : * Reserve public key (to be merged into)
45 : */
46 : struct TALER_ReservePublicKeyP reserve_pub;
47 :
48 : /**
49 : * Reserve private key (useful especially if
50 : * @e reserve_ref is NULL).
51 : */
52 : struct TALER_ReservePrivateKeyP reserve_priv;
53 :
54 : /**
55 : * Handle while operation is running.
56 : */
57 : struct TALER_EXCHANGE_PostPursesMergeHandle *dh;
58 :
59 : /**
60 : * Reference to the merge capability.
61 : */
62 : const char *merge_ref;
63 :
64 : /**
65 : * Reference to the reserve, or NULL (!).
66 : */
67 : const char *reserve_ref;
68 :
69 : /**
70 : * Interpreter state.
71 : */
72 : struct TALER_TESTING_Interpreter *is;
73 :
74 : /**
75 : * Hash of the payto://-URI for the reserve we are
76 : * merging into.
77 : */
78 : struct TALER_NormalizedPaytoHashP h_payto;
79 :
80 : /**
81 : * Set to the KYC requirement row *if* the exchange replied with
82 : * a request for KYC.
83 : */
84 : uint64_t requirement_row;
85 :
86 : /**
87 : * Reserve history entry that corresponds to this operation.
88 : * Will be of type #TALER_EXCHANGE_RTT_MERGE.
89 : */
90 : struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history;
91 :
92 : /**
93 : * Public key of the purse.
94 : */
95 : struct TALER_PurseContractPublicKeyP purse_pub;
96 :
97 : /**
98 : * Public key of the merge capability.
99 : */
100 : struct TALER_PurseMergePublicKeyP merge_pub;
101 :
102 : /**
103 : * Contract value.
104 : */
105 : struct TALER_Amount value_after_fees;
106 :
107 : /**
108 : * Hash of the contract.
109 : */
110 : struct TALER_PrivateContractHashP h_contract_terms;
111 :
112 : /**
113 : * When does the purse expire.
114 : */
115 : struct GNUNET_TIME_Timestamp purse_expiration;
116 :
117 : /**
118 : * Minimum age of deposits into the purse.
119 : */
120 : uint32_t min_age;
121 :
122 : /**
123 : * Expected HTTP response code.
124 : */
125 : unsigned int expected_response_code;
126 :
127 : };
128 :
129 :
130 : /**
131 : * Callback to analyze the /purses/$PID/merge response, just used to check if
132 : * the response code is acceptable.
133 : *
134 : * @param cls closure.
135 : * @param dr merge response details
136 : */
137 : static void
138 6 : merge_cb (void *cls,
139 : const struct TALER_EXCHANGE_PostPursesMergeResponse *dr)
140 : {
141 6 : struct PurseMergeState *ds = cls;
142 :
143 6 : ds->dh = NULL;
144 6 : switch (dr->hr.http_status)
145 : {
146 3 : case MHD_HTTP_OK:
147 3 : ds->reserve_history.type = TALER_EXCHANGE_RTT_MERGE;
148 3 : ds->reserve_history.amount = ds->value_after_fees;
149 3 : GNUNET_assert (GNUNET_OK ==
150 : TALER_amount_set_zero (
151 : ds->value_after_fees.currency,
152 : &ds->reserve_history.details.merge_details.purse_fee));
153 : ds->reserve_history.details.merge_details.h_contract_terms
154 3 : = ds->h_contract_terms;
155 : ds->reserve_history.details.merge_details.merge_pub
156 3 : = ds->merge_pub;
157 : ds->reserve_history.details.merge_details.purse_pub
158 3 : = ds->purse_pub;
159 : ds->reserve_history.details.merge_details.reserve_sig
160 3 : = *dr->reserve_sig;
161 : ds->reserve_history.details.merge_details.merge_timestamp
162 3 : = ds->merge_timestamp;
163 : ds->reserve_history.details.merge_details.purse_expiration
164 3 : = ds->purse_expiration;
165 : ds->reserve_history.details.merge_details.min_age
166 3 : = ds->min_age;
167 : ds->reserve_history.details.merge_details.flags
168 3 : = TALER_WAMF_MODE_MERGE_FULLY_PAID_PURSE;
169 3 : break;
170 1 : case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
171 : /* KYC required */
172 1 : ds->requirement_row =
173 1 : dr->details.unavailable_for_legal_reasons.requirement_row;
174 1 : GNUNET_break (0 ==
175 : GNUNET_memcmp (
176 : &ds->h_payto,
177 : &dr->details.unavailable_for_legal_reasons.h_payto));
178 1 : break;
179 : }
180 :
181 :
182 6 : if (ds->expected_response_code != dr->hr.http_status)
183 : {
184 0 : TALER_TESTING_unexpected_status (ds->is,
185 : dr->hr.http_status,
186 : ds->expected_response_code);
187 0 : return;
188 : }
189 6 : TALER_TESTING_interpreter_next (ds->is);
190 : }
191 :
192 :
193 : /**
194 : * Run the command.
195 : *
196 : * @param cls closure.
197 : * @param cmd the command to execute.
198 : * @param is the interpreter state.
199 : */
200 : static void
201 6 : merge_run (void *cls,
202 : const struct TALER_TESTING_Command *cmd,
203 : struct TALER_TESTING_Interpreter *is)
204 : {
205 6 : struct PurseMergeState *ds = cls;
206 : const struct TALER_PurseMergePrivateKeyP *merge_priv;
207 : const json_t *ct;
208 : const struct TALER_TESTING_Command *ref;
209 :
210 : (void) cmd;
211 6 : ds->is = is;
212 6 : ref = TALER_TESTING_interpreter_lookup_command (ds->is,
213 : ds->merge_ref);
214 6 : GNUNET_assert (NULL != ref);
215 6 : if (GNUNET_OK !=
216 6 : TALER_TESTING_get_trait_merge_priv (ref,
217 : &merge_priv))
218 : {
219 0 : GNUNET_break (0);
220 0 : TALER_TESTING_interpreter_fail (ds->is);
221 0 : return;
222 : }
223 : {
224 : const struct TALER_PurseContractPublicKeyP *purse_pub;
225 :
226 6 : if (GNUNET_OK !=
227 6 : TALER_TESTING_get_trait_purse_pub (ref,
228 : &purse_pub))
229 : {
230 0 : GNUNET_break (0);
231 0 : TALER_TESTING_interpreter_fail (ds->is);
232 0 : return;
233 : }
234 6 : ds->purse_pub = *purse_pub;
235 : }
236 :
237 6 : if (GNUNET_OK !=
238 6 : TALER_TESTING_get_trait_contract_terms (ref,
239 : &ct))
240 : {
241 0 : GNUNET_break (0);
242 0 : TALER_TESTING_interpreter_fail (ds->is);
243 0 : return;
244 : }
245 6 : if (GNUNET_OK !=
246 6 : TALER_JSON_contract_hash (ct,
247 : &ds->h_contract_terms))
248 : {
249 0 : GNUNET_break (0);
250 0 : TALER_TESTING_interpreter_fail (ds->is);
251 0 : return;
252 : }
253 : {
254 : struct GNUNET_JSON_Specification spec[] = {
255 6 : GNUNET_JSON_spec_timestamp ("pay_deadline",
256 : &ds->purse_expiration),
257 6 : TALER_JSON_spec_amount_any ("amount",
258 : &ds->value_after_fees),
259 6 : GNUNET_JSON_spec_mark_optional (
260 : GNUNET_JSON_spec_uint32 ("minimum_age",
261 : &ds->min_age),
262 : NULL),
263 6 : GNUNET_JSON_spec_end ()
264 : };
265 :
266 6 : if (GNUNET_OK !=
267 6 : GNUNET_JSON_parse (ct,
268 : spec,
269 : NULL, NULL))
270 : {
271 0 : GNUNET_break (0);
272 0 : TALER_TESTING_interpreter_fail (ds->is);
273 0 : return;
274 : }
275 : }
276 :
277 6 : if (NULL == ds->reserve_ref)
278 : {
279 0 : GNUNET_CRYPTO_eddsa_key_create (&ds->reserve_priv.eddsa_priv);
280 : }
281 : else
282 : {
283 : const struct TALER_ReservePrivateKeyP *rp;
284 :
285 6 : ref = TALER_TESTING_interpreter_lookup_command (ds->is,
286 : ds->reserve_ref);
287 6 : GNUNET_assert (NULL != ref);
288 6 : if (GNUNET_OK !=
289 6 : TALER_TESTING_get_trait_reserve_priv (ref,
290 : &rp))
291 : {
292 0 : GNUNET_break (0);
293 0 : TALER_TESTING_interpreter_fail (ds->is);
294 0 : return;
295 : }
296 6 : ds->reserve_priv = *rp;
297 : }
298 6 : GNUNET_CRYPTO_eddsa_key_get_public (&ds->reserve_priv.eddsa_priv,
299 : &ds->reserve_pub.eddsa_pub);
300 : {
301 : struct TALER_NormalizedPayto payto_uri;
302 : const char *exchange_url;
303 : const struct TALER_TESTING_Command *exchange_cmd;
304 :
305 6 : exchange_cmd = TALER_TESTING_interpreter_get_command (is,
306 : "exchange");
307 6 : if (NULL == exchange_cmd)
308 : {
309 0 : GNUNET_break (0);
310 0 : TALER_TESTING_interpreter_fail (is);
311 0 : return;
312 : }
313 6 : GNUNET_assert (GNUNET_OK ==
314 : TALER_TESTING_get_trait_exchange_url (exchange_cmd,
315 : &exchange_url));
316 6 : payto_uri = TALER_reserve_make_payto (exchange_url,
317 6 : &ds->reserve_pub);
318 6 : TALER_normalized_payto_hash (payto_uri,
319 : &ds->h_payto);
320 6 : GNUNET_free (payto_uri.normalized_payto);
321 : }
322 6 : GNUNET_CRYPTO_eddsa_key_get_public (&merge_priv->eddsa_priv,
323 : &ds->merge_pub.eddsa_pub);
324 6 : ds->merge_timestamp = GNUNET_TIME_timestamp_get ();
325 6 : ds->dh = TALER_EXCHANGE_post_purses_merge_create (
326 : TALER_TESTING_interpreter_get_context (is),
327 : TALER_TESTING_get_exchange_url (is),
328 : TALER_TESTING_get_keys (is),
329 : NULL, /* no wad */
330 6 : &ds->reserve_priv,
331 6 : &ds->purse_pub,
332 : merge_priv,
333 6 : &ds->h_contract_terms,
334 6 : ds->min_age,
335 6 : &ds->value_after_fees,
336 : ds->purse_expiration,
337 : ds->merge_timestamp);
338 6 : GNUNET_assert (NULL != ds->dh);
339 6 : GNUNET_assert (TALER_EC_NONE ==
340 : TALER_EXCHANGE_post_purses_merge_start (ds->dh,
341 : &merge_cb,
342 : ds));
343 : }
344 :
345 :
346 : /**
347 : * Free the state of a "merge" CMD, and possibly cancel a
348 : * pending operation thereof.
349 : *
350 : * @param cls closure, must be a `struct PurseMergeState`.
351 : * @param cmd the command which is being cleaned up.
352 : */
353 : static void
354 6 : merge_cleanup (void *cls,
355 : const struct TALER_TESTING_Command *cmd)
356 : {
357 6 : struct PurseMergeState *ds = cls;
358 :
359 6 : if (NULL != ds->dh)
360 : {
361 0 : TALER_TESTING_command_incomplete (ds->is,
362 : cmd->label);
363 0 : TALER_EXCHANGE_post_purses_merge_cancel (ds->dh);
364 0 : ds->dh = NULL;
365 : }
366 6 : GNUNET_free (ds);
367 6 : }
368 :
369 :
370 : /**
371 : * Offer internal data from a "merge" CMD, to other commands.
372 : *
373 : * @param cls closure.
374 : * @param[out] ret result.
375 : * @param trait name of the trait.
376 : * @param index index number of the object to offer.
377 : * @return #GNUNET_OK on success.
378 : */
379 : static enum GNUNET_GenericReturnValue
380 20 : merge_traits (void *cls,
381 : const void **ret,
382 : const char *trait,
383 : unsigned int index)
384 : {
385 20 : struct PurseMergeState *ds = cls;
386 : struct TALER_TESTING_Trait traits[] = {
387 : /* history entry MUST be first due to response code logic below! */
388 20 : TALER_TESTING_make_trait_reserve_history (0,
389 20 : &ds->reserve_history),
390 20 : TALER_TESTING_make_trait_reserve_pub (&ds->reserve_pub),
391 20 : TALER_TESTING_make_trait_timestamp (0,
392 20 : &ds->merge_timestamp),
393 20 : TALER_TESTING_make_trait_legi_requirement_row (&ds->requirement_row),
394 20 : TALER_TESTING_make_trait_h_normalized_payto (&ds->h_payto),
395 20 : TALER_TESTING_trait_end ()
396 : };
397 :
398 20 : return TALER_TESTING_get_trait ((ds->expected_response_code == MHD_HTTP_OK)
399 : ? &traits[0] /* we have reserve history */
400 : : &traits[1], /* skip reserve history */
401 : ret,
402 : trait,
403 : index);
404 : }
405 :
406 :
407 : struct TALER_TESTING_Command
408 6 : TALER_TESTING_cmd_purse_merge (
409 : const char *label,
410 : unsigned int expected_http_status,
411 : const char *merge_ref,
412 : const char *reserve_ref)
413 : {
414 : struct PurseMergeState *ds;
415 :
416 6 : ds = GNUNET_new (struct PurseMergeState);
417 6 : ds->merge_ref = merge_ref;
418 6 : ds->reserve_ref = reserve_ref;
419 6 : ds->expected_response_code = expected_http_status;
420 :
421 : {
422 6 : struct TALER_TESTING_Command cmd = {
423 : .cls = ds,
424 : .label = label,
425 : .run = &merge_run,
426 : .cleanup = &merge_cleanup,
427 : .traits = &merge_traits
428 : };
429 :
430 6 : return cmd;
431 : }
432 : }
433 :
434 :
435 : /* end of testing_api_cmd_purse_merge.c */
|