Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2014-2023 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify
6 : it under the terms of the GNU General Public License as
7 : published by the Free Software Foundation; either version 3, or
8 : (at your 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
13 : GNU 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_recoup.c
21 : * @brief Implement the /recoup test command.
22 : * @author Marcello Stanisci
23 : */
24 : #include "taler/taler_json_lib.h"
25 : #include <gnunet/gnunet_curl_lib.h>
26 : struct RecoupState;
27 : #define TALER_EXCHANGE_POST_RECOUP_WITHDRAW_RESULT_CLOSURE struct RecoupState
28 : #include "taler/exchange/post-recoup-withdraw.h"
29 : #include "taler/taler_testing_lib.h"
30 :
31 :
32 : /**
33 : * State for a "pay back" CMD.
34 : */
35 : struct RecoupState
36 : {
37 : /**
38 : * Expected HTTP status code.
39 : */
40 : unsigned int expected_response_code;
41 :
42 : /**
43 : * Command that offers a reserve private key,
44 : * plus a coin to be paid back.
45 : */
46 : const char *coin_reference;
47 :
48 : /**
49 : * The interpreter state.
50 : */
51 : struct TALER_TESTING_Interpreter *is;
52 :
53 : /**
54 : * Handle to the ongoing operation.
55 : */
56 : struct TALER_EXCHANGE_PostRecoupWithdrawHandle *ph;
57 :
58 : /**
59 : * If the recoup filled a reserve, this is set to the reserve's public key.
60 : */
61 : struct TALER_ReservePublicKeyP reserve_pub;
62 :
63 : /**
64 : * Entry in the coin's history generated by this operation.
65 : */
66 : struct TALER_EXCHANGE_CoinHistoryEntry che;
67 :
68 : /**
69 : * Public key of the refunded coin.
70 : */
71 : struct TALER_CoinSpendPublicKeyP coin;
72 :
73 : /**
74 : * Reserve history entry, set if this recoup actually filled up a reserve.
75 : * Otherwise `reserve_history.type` will be zero.
76 : */
77 : struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history;
78 :
79 : };
80 :
81 :
82 : /**
83 : * Check the result of the recoup request: checks whether
84 : * the HTTP response code is good, and that the coin that
85 : * was paid back belonged to the right reserve.
86 : *
87 : * @param ps closure
88 : * @param rr response details
89 : */
90 : static void
91 0 : recoup_cb (struct RecoupState *ps,
92 : const struct TALER_EXCHANGE_PostRecoupWithdrawResponse *rr)
93 : {
94 0 : const struct TALER_EXCHANGE_HttpResponse *hr = &rr->hr;
95 0 : struct TALER_TESTING_Interpreter *is = ps->is;
96 : const struct TALER_TESTING_Command *reserve_cmd;
97 : char *cref;
98 : unsigned int idx;
99 :
100 0 : ps->ph = NULL;
101 0 : if (ps->expected_response_code != hr->http_status)
102 : {
103 0 : TALER_TESTING_unexpected_status (is,
104 : hr->http_status,
105 : ps->expected_response_code);
106 0 : return;
107 : }
108 :
109 0 : if (GNUNET_OK !=
110 0 : TALER_TESTING_parse_coin_reference (
111 : ps->coin_reference,
112 : &cref,
113 : &idx))
114 : {
115 0 : TALER_TESTING_interpreter_fail (is);
116 0 : return;
117 : }
118 : (void) idx; /* do NOT use! We ignore 'idx', must be 0 for melt! */
119 :
120 0 : reserve_cmd = TALER_TESTING_interpreter_lookup_command (is,
121 : cref);
122 0 : GNUNET_free (cref);
123 :
124 0 : if (NULL == reserve_cmd)
125 : {
126 0 : GNUNET_break (0);
127 0 : TALER_TESTING_interpreter_fail (is);
128 0 : return;
129 : }
130 :
131 0 : switch (hr->http_status)
132 : {
133 0 : case MHD_HTTP_OK:
134 : /* check old_coin_pub or reserve_pub, respectively */
135 : {
136 : const struct TALER_ReservePrivateKeyP *reserve_priv;
137 :
138 0 : if (GNUNET_OK !=
139 0 : TALER_TESTING_get_trait_reserve_priv (reserve_cmd,
140 : &reserve_priv))
141 : {
142 0 : GNUNET_break (0);
143 0 : TALER_TESTING_interpreter_fail (is);
144 0 : return;
145 : }
146 0 : GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
147 : &ps->reserve_pub.eddsa_pub);
148 0 : if (0 != GNUNET_memcmp (&rr->details.ok.reserve_pub,
149 : &ps->reserve_pub))
150 : {
151 0 : GNUNET_break (0);
152 0 : TALER_TESTING_interpreter_fail (is);
153 0 : return;
154 : }
155 0 : if (GNUNET_OK ==
156 0 : TALER_amount_is_valid (&ps->reserve_history.amount))
157 0 : ps->reserve_history.type = TALER_EXCHANGE_RTT_RECOUP;
158 : /* ps->reserve_history.details.recoup_details.coin_pub; // initialized earlier */
159 0 : ps->che.details.recoup.reserve_pub = ps->reserve_pub;
160 : }
161 0 : break;
162 0 : case MHD_HTTP_NOT_FOUND:
163 0 : break;
164 0 : case MHD_HTTP_CONFLICT:
165 0 : break;
166 0 : default:
167 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
168 : "Unmanaged HTTP status code %u/%d.\n",
169 : hr->http_status,
170 : (int) hr->ec);
171 0 : break;
172 : }
173 0 : TALER_TESTING_interpreter_next (is);
174 : }
175 :
176 :
177 : /**
178 : * Run the command.
179 : *
180 : * @param cls closure.
181 : * @param cmd the command to execute.
182 : * @param is the interpreter state.
183 : */
184 : static void
185 0 : recoup_run (void *cls,
186 : const struct TALER_TESTING_Command *cmd,
187 : struct TALER_TESTING_Interpreter *is)
188 : {
189 0 : struct RecoupState *ps = cls;
190 : const struct TALER_TESTING_Command *coin_cmd;
191 : const struct TALER_CoinSpendPrivateKeyP *coin_priv;
192 : const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
193 : const struct TALER_DenominationSignature *coin_sig;
194 : const struct TALER_WithdrawMasterSeedP *seed;
195 : const struct TALER_BlindingMasterSeedP *blinding_seed;
196 : const struct TALER_HashBlindedPlanchetsP *h_planchets;
197 : struct TALER_PlanchetMasterSecretP secret;
198 : char *cref;
199 : unsigned int idx;
200 : const struct TALER_ExchangeBlindingValues *ewv;
201 : struct TALER_DenominationHashP h_denom_pub;
202 :
203 0 : ps->is = is;
204 0 : if (GNUNET_OK !=
205 0 : TALER_TESTING_parse_coin_reference (
206 : ps->coin_reference,
207 : &cref,
208 : &idx))
209 : {
210 0 : TALER_TESTING_interpreter_fail (is);
211 0 : return;
212 : }
213 :
214 0 : coin_cmd = TALER_TESTING_interpreter_lookup_command (is,
215 : cref);
216 0 : GNUNET_free (cref);
217 :
218 0 : if (NULL == coin_cmd)
219 : {
220 0 : GNUNET_break (0);
221 0 : TALER_TESTING_interpreter_fail (is);
222 0 : return;
223 : }
224 0 : if (GNUNET_OK !=
225 0 : TALER_TESTING_get_trait_coin_priv (coin_cmd,
226 : idx,
227 : &coin_priv))
228 : {
229 0 : GNUNET_break (0);
230 0 : TALER_TESTING_interpreter_fail (is);
231 0 : return;
232 : }
233 0 : GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
234 : &ps->coin.eddsa_pub);
235 0 : if (GNUNET_OK !=
236 0 : TALER_TESTING_get_trait_exchange_blinding_values (coin_cmd,
237 : idx,
238 : &ewv))
239 : {
240 0 : GNUNET_break (0);
241 0 : TALER_TESTING_interpreter_fail (is);
242 0 : return;
243 : }
244 0 : if (GNUNET_OK !=
245 0 : TALER_TESTING_get_trait_withdraw_seed (coin_cmd,
246 : &seed))
247 : {
248 0 : GNUNET_break (0);
249 0 : TALER_TESTING_interpreter_fail (is);
250 0 : return;
251 : }
252 0 : if (GNUNET_OK !=
253 0 : TALER_TESTING_get_trait_blinding_seed (coin_cmd,
254 : &blinding_seed))
255 : {
256 0 : GNUNET_break (0);
257 0 : TALER_TESTING_interpreter_fail (is);
258 0 : return;
259 : }
260 0 : GNUNET_CRYPTO_eddsa_key_get_public (
261 0 : &coin_priv->eddsa_priv,
262 : &ps->reserve_history.details.recoup_details.coin_pub.eddsa_pub);
263 :
264 0 : if (GNUNET_OK !=
265 0 : TALER_TESTING_get_trait_denom_pub (coin_cmd,
266 : idx,
267 : &denom_pub))
268 : {
269 0 : GNUNET_break (0);
270 0 : TALER_TESTING_interpreter_fail (is);
271 0 : return;
272 : }
273 0 : if (GNUNET_OK !=
274 0 : TALER_TESTING_get_trait_denom_sig (coin_cmd,
275 : idx,
276 : &coin_sig))
277 : {
278 0 : GNUNET_break (0);
279 0 : TALER_TESTING_interpreter_fail (is);
280 0 : return;
281 : }
282 0 : if (GNUNET_OK !=
283 0 : TALER_TESTING_get_trait_withdraw_commitment (coin_cmd,
284 : &h_planchets))
285 : {
286 0 : GNUNET_break (0);
287 0 : TALER_TESTING_interpreter_fail (is);
288 0 : return;
289 : }
290 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
291 : "Trying to recoup denomination '%s'\n",
292 : TALER_B2S (&denom_pub->h_key));
293 0 : ps->che.type = TALER_EXCHANGE_CTT_RECOUP;
294 0 : ps->che.amount = ps->reserve_history.amount;
295 0 : TALER_withdraw_expand_secrets (1,
296 : seed,
297 : &secret);
298 0 : TALER_planchet_blinding_secret_create (&secret,
299 : ewv,
300 : &ps->che.details.recoup.coin_bks);
301 0 : TALER_denom_pub_hash (&denom_pub->key,
302 : &h_denom_pub);
303 0 : TALER_wallet_recoup_sign (&h_denom_pub,
304 0 : &ps->che.details.recoup.coin_bks,
305 : coin_priv,
306 : &ps->che.details.recoup.coin_sig);
307 0 : ps->ph = TALER_EXCHANGE_post_recoup_withdraw_create (
308 : TALER_TESTING_interpreter_get_context (is),
309 : TALER_TESTING_get_exchange_url (is),
310 : TALER_TESTING_get_keys (is),
311 : denom_pub,
312 : coin_sig,
313 : ewv,
314 : blinding_seed,
315 : idx,
316 : &secret,
317 : h_planchets);
318 0 : GNUNET_assert (NULL != ps->ph);
319 0 : GNUNET_assert (TALER_EC_NONE ==
320 : TALER_EXCHANGE_post_recoup_withdraw_start (ps->ph,
321 : &recoup_cb,
322 : ps));
323 : }
324 :
325 :
326 : /**
327 : * Cleanup the "recoup" CMD state, and possibly cancel
328 : * a pending operation thereof.
329 : *
330 : * @param cls closure.
331 : * @param cmd the command which is being cleaned up.
332 : */
333 : static void
334 0 : recoup_cleanup (void *cls,
335 : const struct TALER_TESTING_Command *cmd)
336 : {
337 0 : struct RecoupState *ps = cls;
338 0 : if (NULL != ps->ph)
339 : {
340 0 : TALER_EXCHANGE_post_recoup_withdraw_cancel (ps->ph);
341 0 : ps->ph = NULL;
342 : }
343 0 : GNUNET_free (ps);
344 0 : }
345 :
346 :
347 : /**
348 : * Offer internal data from a "recoup" CMD state to other
349 : * commands.
350 : *
351 : * @param cls closure
352 : * @param[out] ret result (could be anything)
353 : * @param trait name of the trait
354 : * @param index index number of the object to offer.
355 : * @return #GNUNET_OK on success
356 : */
357 : static enum GNUNET_GenericReturnValue
358 0 : recoup_traits (void *cls,
359 : const void **ret,
360 : const char *trait,
361 : unsigned int index)
362 : {
363 0 : struct RecoupState *ps = cls;
364 :
365 0 : if (ps->reserve_history.type != TALER_EXCHANGE_RTT_RECOUP)
366 0 : return GNUNET_SYSERR; /* no traits */
367 : {
368 : struct TALER_TESTING_Trait traits[] = {
369 0 : TALER_TESTING_make_trait_reserve_pub (&ps->reserve_pub),
370 0 : TALER_TESTING_make_trait_reserve_history (0,
371 0 : &ps->reserve_history),
372 0 : TALER_TESTING_make_trait_coin_history (0,
373 0 : &ps->che),
374 0 : TALER_TESTING_make_trait_coin_pub (0,
375 0 : &ps->coin),
376 0 : TALER_TESTING_trait_end ()
377 : };
378 :
379 0 : return TALER_TESTING_get_trait (traits,
380 : ret,
381 : trait,
382 : index);
383 : }
384 : }
385 :
386 :
387 : struct TALER_TESTING_Command
388 0 : TALER_TESTING_cmd_recoup (const char *label,
389 : unsigned int expected_response_code,
390 : const char *coin_reference,
391 : const char *amount)
392 : {
393 : struct RecoupState *ps;
394 :
395 0 : ps = GNUNET_new (struct RecoupState);
396 0 : ps->expected_response_code = expected_response_code;
397 0 : ps->coin_reference = coin_reference;
398 0 : if (GNUNET_OK !=
399 0 : TALER_string_to_amount (amount,
400 : &ps->reserve_history.amount))
401 : {
402 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
403 : "Failed to parse amount `%s' at %s\n",
404 : amount,
405 : label);
406 0 : GNUNET_assert (0);
407 : }
408 : {
409 0 : struct TALER_TESTING_Command cmd = {
410 : .cls = ps,
411 : .label = label,
412 : .run = &recoup_run,
413 : .cleanup = &recoup_cleanup,
414 : .traits = &recoup_traits
415 : };
416 :
417 0 : return cmd;
418 : }
419 : }
|