Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2018-2023 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_auditor_deposit_confirmation.c
21 : * @brief command for testing /deposit_confirmation.
22 : * @author Christian Grothoff
23 : */
24 : #include "taler/platform.h"
25 : #include "taler/taler_json_lib.h"
26 : #include <gnunet/gnunet_curl_lib.h>
27 : #include "taler/taler_auditor_service.h"
28 : #include "taler/taler_testing_lib.h"
29 : #include "taler/taler_signatures.h"
30 : #include "taler/backoff.h"
31 :
32 : /**
33 : * How long do we wait AT MOST when retrying?
34 : */
35 : #define MAX_BACKOFF GNUNET_TIME_relative_multiply ( \
36 : GNUNET_TIME_UNIT_MILLISECONDS, 100)
37 :
38 : /**
39 : * How often do we retry before giving up?
40 : */
41 : #define NUM_RETRIES 5
42 :
43 :
44 : /**
45 : * State for a "deposit confirmation" CMD.
46 : */
47 : struct DepositConfirmationState
48 : {
49 :
50 : /**
51 : * Reference to any command that is able to provide a deposit.
52 : */
53 : const char *deposit_reference;
54 :
55 : /**
56 : * What is the deposited amount without the fee (i.e. the
57 : * amount we expect in the deposit confirmation)?
58 : */
59 : const char *amount_without_fee;
60 :
61 : /**
62 : * How many coins were there in the @e deposit_reference?
63 : */
64 : unsigned int num_coins;
65 :
66 : /**
67 : * DepositConfirmation handle while operation is running.
68 : */
69 : struct TALER_AUDITOR_DepositConfirmationHandle *dc;
70 :
71 : /**
72 : * Interpreter state.
73 : */
74 : struct TALER_TESTING_Interpreter *is;
75 :
76 : /**
77 : * Task scheduled to try later.
78 : */
79 : struct GNUNET_SCHEDULER_Task *retry_task;
80 :
81 : /**
82 : * How long do we wait until we retry?
83 : */
84 : struct GNUNET_TIME_Relative backoff;
85 :
86 : /**
87 : * Expected HTTP response code.
88 : */
89 : unsigned int expected_response_code;
90 :
91 : /**
92 : * How often should we retry on (transient) failures?
93 : */
94 : unsigned int do_retry;
95 :
96 : };
97 :
98 :
99 : /**
100 : * Run the command.
101 : *
102 : * @param cls closure.
103 : * @param cmd the command to execute.
104 : * @param is the interpreter state.
105 : */
106 : static void
107 : deposit_confirmation_run (void *cls,
108 : const struct TALER_TESTING_Command *cmd,
109 : struct TALER_TESTING_Interpreter *is);
110 :
111 :
112 : /**
113 : * Task scheduled to re-try #deposit_confirmation_run.
114 : *
115 : * @param cls a `struct DepositConfirmationState`
116 : */
117 : static void
118 0 : do_retry (void *cls)
119 : {
120 0 : struct DepositConfirmationState *dcs = cls;
121 :
122 0 : dcs->retry_task = NULL;
123 0 : TALER_TESTING_touch_cmd (dcs->is);
124 0 : deposit_confirmation_run (dcs,
125 : NULL,
126 : dcs->is);
127 0 : }
128 :
129 :
130 : /**
131 : * Callback to analyze the /deposit-confirmation response, just used
132 : * to check if the response code is acceptable.
133 : *
134 : * @param cls closure.
135 : * @param dcr response details
136 : */
137 : static void
138 2 : deposit_confirmation_cb (
139 : void *cls,
140 : const struct TALER_AUDITOR_DepositConfirmationResponse *dcr)
141 : {
142 2 : struct DepositConfirmationState *dcs = cls;
143 2 : const struct TALER_AUDITOR_HttpResponse *hr = &dcr->hr;
144 :
145 2 : dcs->dc = NULL;
146 2 : if (dcs->expected_response_code != hr->http_status)
147 : {
148 0 : if (0 != dcs->do_retry)
149 : {
150 0 : dcs->do_retry--;
151 0 : if ( (0 == hr->http_status) ||
152 0 : (TALER_EC_GENERIC_DB_SOFT_FAILURE == hr->ec) ||
153 0 : (MHD_HTTP_INTERNAL_SERVER_ERROR == hr->http_status) )
154 : {
155 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
156 : "Retrying deposit confirmation failed with %u/%d\n",
157 : hr->http_status,
158 : (int) hr->ec);
159 : /* on DB conflicts, do not use backoff */
160 0 : if (TALER_EC_GENERIC_DB_SOFT_FAILURE == hr->ec)
161 0 : dcs->backoff = GNUNET_TIME_UNIT_ZERO;
162 : else
163 0 : dcs->backoff = GNUNET_TIME_randomized_backoff (dcs->backoff,
164 : MAX_BACKOFF);
165 0 : TALER_TESTING_inc_tries (dcs->is);
166 0 : dcs->retry_task = GNUNET_SCHEDULER_add_delayed (dcs->backoff,
167 : &do_retry,
168 : dcs);
169 0 : return;
170 : }
171 : }
172 0 : TALER_TESTING_unexpected_status (dcs->is,
173 : hr->http_status,
174 : dcs->expected_response_code);
175 0 : return;
176 : }
177 2 : TALER_TESTING_interpreter_next (dcs->is);
178 : }
179 :
180 :
181 : /**
182 : * Run the command.
183 : *
184 : * @param cls closure.
185 : * @param cmd the command to execute.
186 : * @param is the interpreter state.
187 : */
188 : static void
189 2 : deposit_confirmation_run (void *cls,
190 : const struct TALER_TESTING_Command *cmd,
191 : struct TALER_TESTING_Interpreter *is)
192 2 : {
193 : static struct TALER_ExtensionPolicyHashP no_h_policy;
194 2 : struct DepositConfirmationState *dcs = cls;
195 : const struct TALER_TESTING_Command *deposit_cmd;
196 : struct TALER_MerchantWireHashP h_wire;
197 : struct TALER_PrivateContractHashP h_contract_terms;
198 2 : const struct GNUNET_TIME_Timestamp *exchange_timestamp = NULL;
199 : struct GNUNET_TIME_Timestamp timestamp;
200 : const struct GNUNET_TIME_Timestamp *wire_deadline;
201 2 : struct GNUNET_TIME_Timestamp refund_deadline
202 : = GNUNET_TIME_UNIT_ZERO_TS;
203 : struct TALER_Amount amount_without_fee;
204 2 : struct TALER_CoinSpendPublicKeyP coin_pubs[dcs->num_coins];
205 2 : const struct TALER_CoinSpendPublicKeyP *coin_pubps[dcs->num_coins];
206 2 : const struct TALER_CoinSpendSignatureP *coin_sigps[dcs->num_coins];
207 : const struct TALER_MerchantPrivateKeyP *merchant_priv;
208 : struct TALER_MerchantPublicKeyP merchant_pub;
209 : const struct TALER_ExchangePublicKeyP *exchange_pub;
210 : const struct TALER_ExchangeSignatureP *exchange_sig;
211 : const json_t *wire_details;
212 : const json_t *contract_terms;
213 : const struct TALER_EXCHANGE_Keys *keys;
214 : const struct TALER_EXCHANGE_SigningPublicKey *spk;
215 : const char *auditor_url;
216 :
217 : (void) cmd;
218 2 : dcs->is = is;
219 2 : GNUNET_assert (NULL != dcs->deposit_reference);
220 : {
221 : const struct TALER_TESTING_Command *auditor_cmd;
222 :
223 : auditor_cmd
224 2 : = TALER_TESTING_interpreter_get_command (is,
225 : "auditor");
226 2 : if (NULL == auditor_cmd)
227 : {
228 0 : GNUNET_break (0);
229 0 : TALER_TESTING_interpreter_fail (is);
230 0 : return;
231 : }
232 2 : if (GNUNET_OK !=
233 2 : TALER_TESTING_get_trait_auditor_url (auditor_cmd,
234 : &auditor_url))
235 : {
236 0 : GNUNET_break (0);
237 0 : TALER_TESTING_interpreter_fail (is);
238 0 : return;
239 : }
240 : }
241 : deposit_cmd
242 2 : = TALER_TESTING_interpreter_lookup_command (is,
243 : dcs->deposit_reference);
244 2 : if (NULL == deposit_cmd)
245 : {
246 0 : GNUNET_break (0);
247 0 : TALER_TESTING_interpreter_fail (is);
248 0 : return;
249 : }
250 :
251 2 : GNUNET_assert (GNUNET_OK ==
252 : TALER_TESTING_get_trait_exchange_pub (deposit_cmd,
253 : 0,
254 : &exchange_pub));
255 2 : GNUNET_assert (GNUNET_OK ==
256 : TALER_TESTING_get_trait_exchange_sig (deposit_cmd,
257 : 0,
258 : &exchange_sig));
259 2 : GNUNET_assert (GNUNET_OK ==
260 : TALER_TESTING_get_trait_timestamp (deposit_cmd,
261 : 0,
262 : &exchange_timestamp));
263 2 : GNUNET_assert (GNUNET_OK ==
264 : TALER_TESTING_get_trait_wire_deadline (deposit_cmd,
265 : 0,
266 : &wire_deadline));
267 2 : GNUNET_assert (NULL != exchange_timestamp);
268 2 : keys = TALER_TESTING_get_keys (is);
269 2 : GNUNET_assert (NULL != keys);
270 2 : spk = TALER_EXCHANGE_get_signing_key_info (keys,
271 : exchange_pub);
272 :
273 2 : GNUNET_assert (GNUNET_OK ==
274 : TALER_TESTING_get_trait_contract_terms (deposit_cmd,
275 : &contract_terms));
276 : /* Very unlikely to fail */
277 2 : GNUNET_assert (NULL != contract_terms);
278 2 : GNUNET_assert (GNUNET_OK ==
279 : TALER_JSON_contract_hash (contract_terms,
280 : &h_contract_terms));
281 2 : GNUNET_assert (GNUNET_OK ==
282 : TALER_TESTING_get_trait_wire_details (deposit_cmd,
283 : &wire_details));
284 2 : GNUNET_assert (GNUNET_OK ==
285 : TALER_JSON_merchant_wire_signature_hash (wire_details,
286 : &h_wire));
287 :
288 4 : for (unsigned int i = 0; i<dcs->num_coins; i++)
289 : {
290 : const struct TALER_CoinSpendPrivateKeyP *coin_priv;
291 :
292 2 : GNUNET_assert (GNUNET_OK ==
293 : TALER_TESTING_get_trait_coin_priv (deposit_cmd,
294 : i,
295 : &coin_priv));
296 2 : GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
297 : &coin_pubs[i].eddsa_pub);
298 2 : coin_pubps[i] = &coin_pubs[i];
299 2 : GNUNET_assert (GNUNET_OK ==
300 : TALER_TESTING_get_trait_coin_sig (deposit_cmd,
301 : i,
302 : &coin_sigps[i]));
303 : }
304 2 : GNUNET_assert (GNUNET_OK ==
305 : TALER_TESTING_get_trait_merchant_priv (deposit_cmd,
306 : &merchant_priv));
307 2 : GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv->eddsa_priv,
308 : &merchant_pub.eddsa_pub);
309 2 : GNUNET_assert (GNUNET_OK ==
310 : TALER_string_to_amount (dcs->amount_without_fee,
311 : &amount_without_fee));
312 : {
313 : struct GNUNET_JSON_Specification spec[] = {
314 : /* timestamp is mandatory */
315 2 : GNUNET_JSON_spec_timestamp ("timestamp",
316 : ×tamp),
317 2 : GNUNET_JSON_spec_mark_optional (
318 : GNUNET_JSON_spec_timestamp ("refund_deadline",
319 : &refund_deadline),
320 : NULL),
321 2 : GNUNET_JSON_spec_end ()
322 : };
323 :
324 2 : if (GNUNET_OK !=
325 2 : GNUNET_JSON_parse (contract_terms,
326 : spec,
327 : NULL, NULL))
328 : {
329 0 : GNUNET_break (0);
330 0 : TALER_TESTING_interpreter_fail (is);
331 0 : return;
332 : }
333 2 : if (GNUNET_TIME_absolute_is_zero (refund_deadline.abs_time))
334 2 : refund_deadline = timestamp;
335 : }
336 2 : dcs->dc = TALER_AUDITOR_deposit_confirmation (
337 : TALER_TESTING_interpreter_get_context (is),
338 : auditor_url,
339 : &h_wire,
340 : &no_h_policy,
341 : &h_contract_terms,
342 : *exchange_timestamp,
343 : *wire_deadline,
344 : refund_deadline,
345 : &amount_without_fee,
346 : dcs->num_coins,
347 : coin_pubps,
348 : coin_sigps,
349 : &merchant_pub,
350 : exchange_pub,
351 : exchange_sig,
352 : &keys->master_pub,
353 : spk->valid_from,
354 : spk->valid_until,
355 : spk->valid_legal,
356 : &spk->master_sig,
357 : &deposit_confirmation_cb,
358 : dcs);
359 :
360 2 : if (NULL == dcs->dc)
361 : {
362 0 : GNUNET_break (0);
363 0 : TALER_TESTING_interpreter_fail (is);
364 0 : return;
365 : }
366 2 : return;
367 : }
368 :
369 :
370 : /**
371 : * Free the state of a "deposit_confirmation" CMD, and possibly cancel a
372 : * pending operation thereof.
373 : *
374 : * @param cls closure, a `struct DepositConfirmationState`
375 : * @param cmd the command which is being cleaned up.
376 : */
377 : static void
378 2 : deposit_confirmation_cleanup (void *cls,
379 : const struct TALER_TESTING_Command *cmd)
380 : {
381 2 : struct DepositConfirmationState *dcs = cls;
382 :
383 2 : if (NULL != dcs->dc)
384 : {
385 0 : TALER_TESTING_command_incomplete (dcs->is,
386 : cmd->label);
387 0 : TALER_AUDITOR_deposit_confirmation_cancel (dcs->dc);
388 0 : dcs->dc = NULL;
389 : }
390 2 : if (NULL != dcs->retry_task)
391 : {
392 0 : GNUNET_SCHEDULER_cancel (dcs->retry_task);
393 0 : dcs->retry_task = NULL;
394 : }
395 2 : GNUNET_free (dcs);
396 2 : }
397 :
398 :
399 : struct TALER_TESTING_Command
400 2 : TALER_TESTING_cmd_deposit_confirmation (const char *label,
401 : const char *deposit_reference,
402 : unsigned int num_coins,
403 : const char *amount_without_fee,
404 : unsigned int expected_response_code)
405 : {
406 : struct DepositConfirmationState *dcs;
407 :
408 2 : dcs = GNUNET_new (struct DepositConfirmationState);
409 2 : dcs->deposit_reference = deposit_reference;
410 2 : dcs->num_coins = num_coins;
411 2 : dcs->amount_without_fee = amount_without_fee;
412 2 : dcs->expected_response_code = expected_response_code;
413 :
414 : {
415 2 : struct TALER_TESTING_Command cmd = {
416 : .cls = dcs,
417 : .label = label,
418 : .run = &deposit_confirmation_run,
419 : .cleanup = &deposit_confirmation_cleanup
420 : };
421 :
422 2 : return cmd;
423 : }
424 : }
425 :
426 :
427 : struct TALER_TESTING_Command
428 0 : TALER_TESTING_cmd_deposit_confirmation_with_retry (
429 : struct TALER_TESTING_Command cmd)
430 : {
431 : struct DepositConfirmationState *dcs;
432 :
433 0 : GNUNET_assert (&deposit_confirmation_run == cmd.run);
434 0 : dcs = cmd.cls;
435 0 : dcs->do_retry = NUM_RETRIES;
436 0 : return cmd;
437 : }
438 :
439 :
440 : /* end of testing_auditor_api_cmd_deposit_confirmation.c */
|