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