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