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_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_PaytoHashP 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 0 : merge_cb (void *cls,
139 : const struct TALER_EXCHANGE_AccountMergeResponse *dr)
140 : {
141 0 : struct PurseMergeState *ds = cls;
142 :
143 0 : ds->dh = NULL;
144 0 : switch (dr->hr.http_status)
145 : {
146 0 : case MHD_HTTP_OK:
147 0 : ds->reserve_history.type = TALER_EXCHANGE_RTT_MERGE;
148 0 : ds->reserve_history.amount = ds->value_after_fees;
149 0 : 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 0 : = ds->h_contract_terms;
155 : ds->reserve_history.details.merge_details.merge_pub
156 0 : = ds->merge_pub;
157 : ds->reserve_history.details.merge_details.purse_pub
158 0 : = ds->purse_pub;
159 : ds->reserve_history.details.merge_details.reserve_sig
160 0 : = *dr->reserve_sig;
161 : ds->reserve_history.details.merge_details.merge_timestamp
162 0 : = ds->merge_timestamp;
163 : ds->reserve_history.details.merge_details.purse_expiration
164 0 : = ds->purse_expiration;
165 : ds->reserve_history.details.merge_details.min_age
166 0 : = ds->min_age;
167 : ds->reserve_history.details.merge_details.flags
168 0 : = TALER_WAMF_MODE_MERGE_FULLY_PAID_PURSE;
169 0 : break;
170 0 : case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
171 : /* KYC required */
172 0 : ds->requirement_row =
173 0 : dr->details.unavailable_for_legal_reasons.requirement_row;
174 0 : break;
175 : }
176 :
177 :
178 0 : if (ds->expected_response_code != dr->hr.http_status)
179 : {
180 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
181 : "Unexpected response code %u to command %s in %s:%u\n",
182 : dr->hr.http_status,
183 : ds->is->commands[ds->is->ip].label,
184 : __FILE__,
185 : __LINE__);
186 0 : json_dumpf (dr->hr.reply,
187 : stderr,
188 : 0);
189 0 : TALER_TESTING_interpreter_fail (ds->is);
190 0 : return;
191 : }
192 0 : TALER_TESTING_interpreter_next (ds->is);
193 : }
194 :
195 :
196 : /**
197 : * Run the command.
198 : *
199 : * @param cls closure.
200 : * @param cmd the command to execute.
201 : * @param is the interpreter state.
202 : */
203 : static void
204 0 : merge_run (void *cls,
205 : const struct TALER_TESTING_Command *cmd,
206 : struct TALER_TESTING_Interpreter *is)
207 : {
208 0 : struct PurseMergeState *ds = cls;
209 : const struct TALER_PurseMergePrivateKeyP *merge_priv;
210 : const json_t *ct;
211 : const struct TALER_TESTING_Command *ref;
212 :
213 : (void) cmd;
214 0 : ds->is = is;
215 0 : ref = TALER_TESTING_interpreter_lookup_command (ds->is,
216 : ds->merge_ref);
217 0 : GNUNET_assert (NULL != ref);
218 0 : if (GNUNET_OK !=
219 0 : TALER_TESTING_get_trait_merge_priv (ref,
220 : &merge_priv))
221 : {
222 0 : GNUNET_break (0);
223 0 : TALER_TESTING_interpreter_fail (ds->is);
224 0 : return;
225 : }
226 : {
227 : const struct TALER_PurseContractPublicKeyP *purse_pub;
228 :
229 0 : if (GNUNET_OK !=
230 0 : TALER_TESTING_get_trait_purse_pub (ref,
231 : &purse_pub))
232 : {
233 0 : GNUNET_break (0);
234 0 : TALER_TESTING_interpreter_fail (ds->is);
235 0 : return;
236 : }
237 0 : ds->purse_pub = *purse_pub;
238 : }
239 :
240 0 : if (GNUNET_OK !=
241 0 : TALER_TESTING_get_trait_contract_terms (ref,
242 : &ct))
243 : {
244 0 : GNUNET_break (0);
245 0 : TALER_TESTING_interpreter_fail (ds->is);
246 0 : return;
247 : }
248 0 : if (GNUNET_OK !=
249 0 : TALER_JSON_contract_hash (ct,
250 : &ds->h_contract_terms))
251 : {
252 0 : GNUNET_break (0);
253 0 : TALER_TESTING_interpreter_fail (ds->is);
254 0 : return;
255 : }
256 : {
257 : struct GNUNET_JSON_Specification spec[] = {
258 0 : GNUNET_JSON_spec_timestamp ("pay_deadline",
259 : &ds->purse_expiration),
260 0 : TALER_JSON_spec_amount_any ("amount",
261 : &ds->value_after_fees),
262 0 : GNUNET_JSON_spec_mark_optional (
263 : GNUNET_JSON_spec_uint32 ("minimum_age",
264 : &ds->min_age),
265 : NULL),
266 0 : GNUNET_JSON_spec_end ()
267 : };
268 :
269 0 : if (GNUNET_OK !=
270 0 : GNUNET_JSON_parse (ct,
271 : spec,
272 : NULL, NULL))
273 : {
274 0 : GNUNET_break (0);
275 0 : TALER_TESTING_interpreter_fail (ds->is);
276 0 : return;
277 : }
278 : }
279 :
280 0 : if (NULL == ds->reserve_ref)
281 : {
282 0 : GNUNET_CRYPTO_eddsa_key_create (&ds->reserve_priv.eddsa_priv);
283 : }
284 : else
285 : {
286 : const struct TALER_ReservePrivateKeyP *rp;
287 :
288 0 : ref = TALER_TESTING_interpreter_lookup_command (ds->is,
289 : ds->reserve_ref);
290 0 : GNUNET_assert (NULL != ref);
291 0 : if (GNUNET_OK !=
292 0 : TALER_TESTING_get_trait_reserve_priv (ref,
293 : &rp))
294 : {
295 0 : GNUNET_break (0);
296 0 : TALER_TESTING_interpreter_fail (ds->is);
297 0 : return;
298 : }
299 0 : ds->reserve_priv = *rp;
300 : }
301 0 : GNUNET_CRYPTO_eddsa_key_get_public (&ds->reserve_priv.eddsa_priv,
302 : &ds->reserve_pub.eddsa_pub);
303 : {
304 : char *payto_uri;
305 :
306 0 : payto_uri = TALER_reserve_make_payto (is->exchange_url,
307 0 : &ds->reserve_pub);
308 0 : TALER_payto_hash (payto_uri,
309 : &ds->h_payto);
310 0 : GNUNET_free (payto_uri);
311 : }
312 0 : GNUNET_CRYPTO_eddsa_key_get_public (&merge_priv->eddsa_priv,
313 : &ds->merge_pub.eddsa_pub);
314 0 : ds->merge_timestamp = GNUNET_TIME_timestamp_get ();
315 0 : ds->dh = TALER_EXCHANGE_account_merge (
316 : is->exchange,
317 : NULL, /* no wad */
318 0 : &ds->reserve_priv,
319 0 : &ds->purse_pub,
320 : merge_priv,
321 0 : &ds->h_contract_terms,
322 0 : ds->min_age,
323 0 : &ds->value_after_fees,
324 : ds->purse_expiration,
325 : ds->merge_timestamp,
326 : &merge_cb,
327 : ds);
328 0 : if (NULL == ds->dh)
329 : {
330 0 : GNUNET_break (0);
331 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
332 : "Could not merge purse\n");
333 0 : TALER_TESTING_interpreter_fail (is);
334 0 : return;
335 : }
336 : }
337 :
338 :
339 : /**
340 : * Free the state of a "merge" CMD, and possibly cancel a
341 : * pending operation thereof.
342 : *
343 : * @param cls closure, must be a `struct PurseMergeState`.
344 : * @param cmd the command which is being cleaned up.
345 : */
346 : static void
347 0 : merge_cleanup (void *cls,
348 : const struct TALER_TESTING_Command *cmd)
349 : {
350 0 : struct PurseMergeState *ds = cls;
351 :
352 0 : if (NULL != ds->dh)
353 : {
354 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
355 : "Command %u (%s) did not complete\n",
356 : ds->is->ip,
357 : cmd->label);
358 0 : TALER_EXCHANGE_account_merge_cancel (ds->dh);
359 0 : ds->dh = NULL;
360 : }
361 0 : GNUNET_free (ds);
362 0 : }
363 :
364 :
365 : /**
366 : * Offer internal data from a "merge" CMD, to other commands.
367 : *
368 : * @param cls closure.
369 : * @param[out] ret result.
370 : * @param trait name of the trait.
371 : * @param index index number of the object to offer.
372 : * @return #GNUNET_OK on success.
373 : */
374 : static enum GNUNET_GenericReturnValue
375 0 : merge_traits (void *cls,
376 : const void **ret,
377 : const char *trait,
378 : unsigned int index)
379 : {
380 0 : struct PurseMergeState *ds = cls;
381 : struct TALER_TESTING_Trait traits[] = {
382 : /* history entry MUST be first due to response code logic below! */
383 0 : TALER_TESTING_make_trait_reserve_history (0,
384 0 : &ds->reserve_history),
385 0 : TALER_TESTING_make_trait_reserve_pub (&ds->reserve_pub),
386 0 : TALER_TESTING_make_trait_timestamp (0,
387 0 : &ds->merge_timestamp),
388 0 : TALER_TESTING_make_trait_legi_requirement_row (&ds->requirement_row),
389 0 : TALER_TESTING_make_trait_h_payto (&ds->h_payto),
390 0 : TALER_TESTING_trait_end ()
391 : };
392 :
393 0 : return TALER_TESTING_get_trait ((ds->expected_response_code == MHD_HTTP_OK)
394 : ? &traits[0] /* we have reserve history */
395 : : &traits[1], /* skip reserve history */
396 : ret,
397 : trait,
398 : index);
399 : }
400 :
401 :
402 : struct TALER_TESTING_Command
403 0 : TALER_TESTING_cmd_purse_merge (
404 : const char *label,
405 : unsigned int expected_http_status,
406 : const char *merge_ref,
407 : const char *reserve_ref)
408 : {
409 : struct PurseMergeState *ds;
410 :
411 0 : ds = GNUNET_new (struct PurseMergeState);
412 0 : ds->merge_ref = merge_ref;
413 0 : ds->reserve_ref = reserve_ref;
414 0 : ds->expected_response_code = expected_http_status;
415 :
416 : {
417 0 : struct TALER_TESTING_Command cmd = {
418 : .cls = ds,
419 : .label = label,
420 : .run = &merge_run,
421 : .cleanup = &merge_cleanup,
422 : .traits = &merge_traits
423 : };
424 :
425 0 : return cmd;
426 : }
427 : }
428 :
429 :
430 : /* end of testing_api_cmd_purse_merge.c */
|