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