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