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 "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 : /**
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_AccountMergeHandle *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_AccountMergeResponse *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_account_merge (
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 : &merge_cb,
339 : ds);
340 6 : if (NULL == ds->dh)
341 : {
342 0 : GNUNET_break (0);
343 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
344 : "Could not merge purse\n");
345 0 : TALER_TESTING_interpreter_fail (is);
346 0 : return;
347 : }
348 : }
349 :
350 :
351 : /**
352 : * Free the state of a "merge" CMD, and possibly cancel a
353 : * pending operation thereof.
354 : *
355 : * @param cls closure, must be a `struct PurseMergeState`.
356 : * @param cmd the command which is being cleaned up.
357 : */
358 : static void
359 6 : merge_cleanup (void *cls,
360 : const struct TALER_TESTING_Command *cmd)
361 : {
362 6 : struct PurseMergeState *ds = cls;
363 :
364 6 : if (NULL != ds->dh)
365 : {
366 0 : TALER_TESTING_command_incomplete (ds->is,
367 : cmd->label);
368 0 : TALER_EXCHANGE_account_merge_cancel (ds->dh);
369 0 : ds->dh = NULL;
370 : }
371 6 : GNUNET_free (ds);
372 6 : }
373 :
374 :
375 : /**
376 : * Offer internal data from a "merge" CMD, to other commands.
377 : *
378 : * @param cls closure.
379 : * @param[out] ret result.
380 : * @param trait name of the trait.
381 : * @param index index number of the object to offer.
382 : * @return #GNUNET_OK on success.
383 : */
384 : static enum GNUNET_GenericReturnValue
385 20 : merge_traits (void *cls,
386 : const void **ret,
387 : const char *trait,
388 : unsigned int index)
389 : {
390 20 : struct PurseMergeState *ds = cls;
391 : struct TALER_TESTING_Trait traits[] = {
392 : /* history entry MUST be first due to response code logic below! */
393 20 : TALER_TESTING_make_trait_reserve_history (0,
394 20 : &ds->reserve_history),
395 20 : TALER_TESTING_make_trait_reserve_pub (&ds->reserve_pub),
396 20 : TALER_TESTING_make_trait_timestamp (0,
397 20 : &ds->merge_timestamp),
398 20 : TALER_TESTING_make_trait_legi_requirement_row (&ds->requirement_row),
399 20 : TALER_TESTING_make_trait_h_normalized_payto (&ds->h_payto),
400 20 : TALER_TESTING_trait_end ()
401 : };
402 :
403 20 : return TALER_TESTING_get_trait ((ds->expected_response_code == MHD_HTTP_OK)
404 : ? &traits[0] /* we have reserve history */
405 : : &traits[1], /* skip reserve history */
406 : ret,
407 : trait,
408 : index);
409 : }
410 :
411 :
412 : struct TALER_TESTING_Command
413 6 : TALER_TESTING_cmd_purse_merge (
414 : const char *label,
415 : unsigned int expected_http_status,
416 : const char *merge_ref,
417 : const char *reserve_ref)
418 : {
419 : struct PurseMergeState *ds;
420 :
421 6 : ds = GNUNET_new (struct PurseMergeState);
422 6 : ds->merge_ref = merge_ref;
423 6 : ds->reserve_ref = reserve_ref;
424 6 : ds->expected_response_code = expected_http_status;
425 :
426 : {
427 6 : struct TALER_TESTING_Command cmd = {
428 : .cls = ds,
429 : .label = label,
430 : .run = &merge_run,
431 : .cleanup = &merge_cleanup,
432 : .traits = &merge_traits
433 : };
434 :
435 6 : return cmd;
436 : }
437 : }
438 :
439 :
440 : /* end of testing_api_cmd_purse_merge.c */
|