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 : const struct TALER_Amount *cumulative_total;
205 2 : struct TALER_CoinSpendPublicKeyP coin_pubs[dcs->num_coins];
206 2 : const struct TALER_CoinSpendPublicKeyP *coin_pubps[dcs->num_coins];
207 2 : const struct TALER_CoinSpendSignatureP *coin_sigps[dcs->num_coins];
208 : const struct TALER_MerchantPrivateKeyP *merchant_priv;
209 : struct TALER_MerchantPublicKeyP merchant_pub;
210 : const struct TALER_ExchangePublicKeyP *exchange_pub;
211 : const struct TALER_ExchangeSignatureP *exchange_sig;
212 : const json_t *wire_details;
213 : const json_t *contract_terms;
214 : const struct TALER_EXCHANGE_Keys *keys;
215 : const struct TALER_EXCHANGE_SigningPublicKey *spk;
216 : const char *auditor_url;
217 :
218 : (void) cmd;
219 2 : dcs->is = is;
220 2 : GNUNET_assert (NULL != dcs->deposit_reference);
221 : {
222 : const struct TALER_TESTING_Command *auditor_cmd;
223 :
224 : auditor_cmd
225 2 : = TALER_TESTING_interpreter_get_command (is,
226 : "auditor");
227 2 : if (NULL == auditor_cmd)
228 : {
229 0 : GNUNET_break (0);
230 0 : TALER_TESTING_interpreter_fail (is);
231 0 : return;
232 : }
233 2 : if (GNUNET_OK !=
234 2 : TALER_TESTING_get_trait_auditor_url (auditor_cmd,
235 : &auditor_url))
236 : {
237 0 : GNUNET_break (0);
238 0 : TALER_TESTING_interpreter_fail (is);
239 0 : return;
240 : }
241 : }
242 : deposit_cmd
243 2 : = TALER_TESTING_interpreter_lookup_command (is,
244 : dcs->deposit_reference);
245 2 : if (NULL == deposit_cmd)
246 : {
247 0 : GNUNET_break (0);
248 0 : TALER_TESTING_interpreter_fail (is);
249 0 : return;
250 : }
251 :
252 2 : GNUNET_assert (GNUNET_OK ==
253 : TALER_TESTING_get_trait_exchange_pub (deposit_cmd,
254 : 0,
255 : &exchange_pub));
256 2 : GNUNET_assert (GNUNET_OK ==
257 : TALER_TESTING_get_trait_exchange_sig (deposit_cmd,
258 : 0,
259 : &exchange_sig));
260 2 : GNUNET_assert (GNUNET_OK ==
261 : TALER_TESTING_get_trait_amount (deposit_cmd,
262 : &cumulative_total));
263 2 : GNUNET_assert (GNUNET_OK ==
264 : TALER_TESTING_get_trait_timestamp (deposit_cmd,
265 : 0,
266 : &exchange_timestamp));
267 2 : GNUNET_assert (GNUNET_OK ==
268 : TALER_TESTING_get_trait_wire_deadline (deposit_cmd,
269 : 0,
270 : &wire_deadline));
271 2 : GNUNET_assert (NULL != exchange_timestamp);
272 2 : keys = TALER_TESTING_get_keys (is);
273 2 : GNUNET_assert (NULL != keys);
274 2 : spk = TALER_EXCHANGE_get_signing_key_info (keys,
275 : exchange_pub);
276 :
277 2 : GNUNET_assert (GNUNET_OK ==
278 : TALER_TESTING_get_trait_contract_terms (deposit_cmd,
279 : &contract_terms));
280 : /* Very unlikely to fail */
281 2 : GNUNET_assert (NULL != contract_terms);
282 2 : GNUNET_assert (GNUNET_OK ==
283 : TALER_JSON_contract_hash (contract_terms,
284 : &h_contract_terms));
285 2 : GNUNET_assert (GNUNET_OK ==
286 : TALER_TESTING_get_trait_wire_details (deposit_cmd,
287 : &wire_details));
288 2 : GNUNET_assert (GNUNET_OK ==
289 : TALER_JSON_merchant_wire_signature_hash (wire_details,
290 : &h_wire));
291 :
292 4 : for (unsigned int i = 0; i<dcs->num_coins; i++)
293 : {
294 : const struct TALER_CoinSpendPrivateKeyP *coin_priv;
295 :
296 2 : GNUNET_assert (GNUNET_OK ==
297 : TALER_TESTING_get_trait_coin_priv (deposit_cmd,
298 : i,
299 : &coin_priv));
300 2 : GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
301 : &coin_pubs[i].eddsa_pub);
302 2 : coin_pubps[i] = &coin_pubs[i];
303 2 : GNUNET_assert (GNUNET_OK ==
304 : TALER_TESTING_get_trait_coin_sig (deposit_cmd,
305 : i,
306 : &coin_sigps[i]));
307 : }
308 2 : GNUNET_assert (GNUNET_OK ==
309 : TALER_TESTING_get_trait_merchant_priv (deposit_cmd,
310 : &merchant_priv));
311 2 : GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv->eddsa_priv,
312 : &merchant_pub.eddsa_pub);
313 2 : GNUNET_assert (GNUNET_OK ==
314 : TALER_string_to_amount (dcs->amount_without_fee,
315 : &amount_without_fee));
316 : {
317 : struct GNUNET_JSON_Specification spec[] = {
318 : /* timestamp is mandatory */
319 2 : GNUNET_JSON_spec_timestamp ("timestamp",
320 : ×tamp),
321 2 : GNUNET_JSON_spec_mark_optional (
322 : GNUNET_JSON_spec_timestamp ("refund_deadline",
323 : &refund_deadline),
324 : NULL),
325 2 : GNUNET_JSON_spec_end ()
326 : };
327 :
328 2 : if (GNUNET_OK !=
329 2 : GNUNET_JSON_parse (contract_terms,
330 : spec,
331 : NULL, NULL))
332 : {
333 0 : GNUNET_break (0);
334 0 : TALER_TESTING_interpreter_fail (is);
335 0 : return;
336 : }
337 2 : if (GNUNET_TIME_absolute_is_zero (refund_deadline.abs_time))
338 2 : refund_deadline = timestamp;
339 : }
340 2 : if (-1 ==
341 2 : TALER_amount_cmp (cumulative_total,
342 : &amount_without_fee))
343 : {
344 :
345 : /* Cumulative must not below the amount we deposited this time */
346 0 : GNUNET_break (0);
347 0 : TALER_TESTING_interpreter_fail (is);
348 0 : return;
349 : }
350 2 : dcs->dc = TALER_AUDITOR_deposit_confirmation (
351 : TALER_TESTING_interpreter_get_context (is),
352 : auditor_url,
353 : &h_wire,
354 : &no_h_policy,
355 : &h_contract_terms,
356 : *exchange_timestamp,
357 : *wire_deadline,
358 : refund_deadline,
359 : cumulative_total,
360 : dcs->num_coins,
361 : coin_pubps,
362 : coin_sigps,
363 : &merchant_pub,
364 : exchange_pub,
365 : exchange_sig,
366 : &keys->master_pub,
367 : spk->valid_from,
368 : spk->valid_until,
369 : spk->valid_legal,
370 : &spk->master_sig,
371 : &deposit_confirmation_cb,
372 : dcs);
373 :
374 2 : if (NULL == dcs->dc)
375 : {
376 0 : GNUNET_break (0);
377 0 : TALER_TESTING_interpreter_fail (is);
378 0 : return;
379 : }
380 2 : return;
381 : }
382 :
383 :
384 : /**
385 : * Free the state of a "deposit_confirmation" CMD, and possibly cancel a
386 : * pending operation thereof.
387 : *
388 : * @param cls closure, a `struct DepositConfirmationState`
389 : * @param cmd the command which is being cleaned up.
390 : */
391 : static void
392 2 : deposit_confirmation_cleanup (void *cls,
393 : const struct TALER_TESTING_Command *cmd)
394 : {
395 2 : struct DepositConfirmationState *dcs = cls;
396 :
397 2 : if (NULL != dcs->dc)
398 : {
399 0 : TALER_TESTING_command_incomplete (dcs->is,
400 : cmd->label);
401 0 : TALER_AUDITOR_deposit_confirmation_cancel (dcs->dc);
402 0 : dcs->dc = NULL;
403 : }
404 2 : if (NULL != dcs->retry_task)
405 : {
406 0 : GNUNET_SCHEDULER_cancel (dcs->retry_task);
407 0 : dcs->retry_task = NULL;
408 : }
409 2 : GNUNET_free (dcs);
410 2 : }
411 :
412 :
413 : struct TALER_TESTING_Command
414 2 : TALER_TESTING_cmd_deposit_confirmation (const char *label,
415 : const char *deposit_reference,
416 : unsigned int num_coins,
417 : const char *amount_without_fee,
418 : unsigned int expected_response_code)
419 : {
420 : struct DepositConfirmationState *dcs;
421 :
422 2 : dcs = GNUNET_new (struct DepositConfirmationState);
423 2 : dcs->deposit_reference = deposit_reference;
424 2 : dcs->num_coins = num_coins;
425 2 : dcs->amount_without_fee = amount_without_fee;
426 2 : dcs->expected_response_code = expected_response_code;
427 :
428 : {
429 2 : struct TALER_TESTING_Command cmd = {
430 : .cls = dcs,
431 : .label = label,
432 : .run = &deposit_confirmation_run,
433 : .cleanup = &deposit_confirmation_cleanup
434 : };
435 :
436 2 : return cmd;
437 : }
438 : }
439 :
440 :
441 : struct TALER_TESTING_Command
442 0 : TALER_TESTING_cmd_deposit_confirmation_with_retry (
443 : struct TALER_TESTING_Command cmd)
444 : {
445 : struct DepositConfirmationState *dcs;
446 :
447 0 : GNUNET_assert (&deposit_confirmation_run == cmd.run);
448 0 : dcs = cmd.cls;
449 0 : dcs->do_retry = NUM_RETRIES;
450 0 : return cmd;
451 : }
452 :
453 :
454 : /* end of testing_auditor_api_cmd_deposit_confirmation.c */
|