Line data Source code
1 : /*
2 : This file is part of TALER
3 : (C) 2020, 2021 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU Affero General Public License as published by the Free Software
7 : Foundation; either version 3, or (at your option) any later version.
8 :
9 : TALER is distributed in the hope that it will be useful, but WITHOUT ANY
10 : WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11 : A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12 :
13 : You should have received a copy of the GNU General Public License along with
14 : TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file taler-merchant-httpd_reserves.c
18 : * @brief logic for initially tracking a reserve's status
19 : * @author Christian Grothoff
20 : */
21 : #include "platform.h"
22 : #include <taler/taler_json_lib.h>
23 : #include "taler-merchant-httpd.h"
24 : #include "taler-merchant-httpd_exchanges.h"
25 : #include "taler-merchant-httpd_reserves.h"
26 :
27 : /**
28 : * How long do we keep the long-poller open?
29 : * Not very long here, as if the money has not
30 : * yet arrived, there is a fair chance that it'll
31 : * take much longer, and in that case we rather
32 : * enter into the delay created by try_later().
33 : */
34 : #define LONGPOLL_DELAY GNUNET_TIME_UNIT_MINUTES
35 :
36 : /**
37 : * Our representation of a reserve that we are (still) checking the status of.
38 : */
39 : struct Reserve
40 : {
41 :
42 : /**
43 : * Kept in a DLL.
44 : */
45 : struct Reserve *next;
46 :
47 : /**
48 : * Kept in a DLL.
49 : */
50 : struct Reserve *prev;
51 :
52 : /**
53 : * Reserve's public key.
54 : */
55 : struct TALER_ReservePublicKeyP reserve_pub;
56 :
57 : /**
58 : * Amount the merchant expects to see in the reserve initially.
59 : * We log a warning if there is a mismatch.
60 : */
61 : struct TALER_Amount expected_amount;
62 :
63 : /**
64 : * URL of the exchange hosting this reserve.
65 : */
66 : char *exchange_url;
67 :
68 : /**
69 : * Instance this reserve belongs with.
70 : */
71 : char *instance_id;
72 :
73 : /**
74 : * Active find operation for this reserve.
75 : */
76 : struct TMH_EXCHANGES_FindOperation *fo;
77 :
78 : /**
79 : * Task scheduled waiting for a timeout for this reserve.
80 : */
81 : struct GNUNET_SCHEDULER_Task *tt;
82 :
83 : /**
84 : * Get operation with the exchange.
85 : */
86 : struct TALER_EXCHANGE_ReservesGetHandle *gh;
87 :
88 : /**
89 : * How long do we wait before trying this reserve again?
90 : */
91 : struct GNUNET_TIME_Relative delay;
92 :
93 : };
94 :
95 :
96 : /**
97 : * Head of DLL of pending reserves.
98 : */
99 : static struct Reserve *reserves_head;
100 :
101 : /**
102 : * Tail of DLL of pending reserves.
103 : */
104 : static struct Reserve *reserves_tail;
105 :
106 :
107 : /**
108 : * Function called to probe a reserve now.
109 : *
110 : * @param cls a `struct Reserve` to query
111 : */
112 : static void
113 : try_now (void *cls);
114 :
115 :
116 : /**
117 : * Free reserve data structure.
118 : *
119 : * @param r reserve to free
120 : */
121 : static void
122 0 : free_reserve (struct Reserve *r)
123 : {
124 0 : GNUNET_CONTAINER_DLL_remove (reserves_head,
125 : reserves_tail,
126 : r);
127 0 : if (NULL != r->fo)
128 : {
129 0 : TMH_EXCHANGES_find_exchange_cancel (r->fo);
130 0 : r->fo = NULL;
131 : }
132 0 : if (NULL != r->gh)
133 : {
134 0 : TALER_EXCHANGE_reserves_get_cancel (r->gh);
135 0 : r->gh = NULL;
136 : }
137 0 : if (NULL != r->tt)
138 : {
139 0 : GNUNET_SCHEDULER_cancel (r->tt);
140 0 : r->tt = NULL;
141 : }
142 0 : GNUNET_free (r->exchange_url);
143 0 : GNUNET_free (r->instance_id);
144 0 : GNUNET_free (r);
145 0 : }
146 :
147 :
148 : /**
149 : * Schedule a job to probe a reserve later again.
150 : *
151 : * @param r reserve to try again later
152 : */
153 : static void
154 0 : try_later (struct Reserve *r)
155 : {
156 : /* minimum delay is the #LONGPOLL_DELAY */
157 0 : r->delay = GNUNET_TIME_relative_max (LONGPOLL_DELAY,
158 : r->delay);
159 : /* STD_BACKOFF has a maximum of 15 minutes */
160 0 : r->delay = GNUNET_TIME_STD_BACKOFF (r->delay);
161 0 : r->tt = GNUNET_SCHEDULER_add_delayed (r->delay,
162 : &try_now,
163 : r);
164 0 : }
165 :
166 :
167 : /**
168 : * Callbacks of this type are used to serve the result of submitting a
169 : * reserve status request to a exchange.
170 : *
171 : * @param cls closure with a `struct Reserve *`
172 : * @param rs HTTP response data
173 : */
174 : static void
175 0 : reserve_cb (void *cls,
176 : const struct TALER_EXCHANGE_ReserveSummary *rs)
177 : {
178 0 : struct Reserve *r = cls;
179 : enum GNUNET_DB_QueryStatus qs;
180 :
181 0 : r->gh = NULL;
182 0 : if (MHD_HTTP_OK != rs->hr.http_status)
183 : {
184 0 : try_later (r);
185 0 : return;
186 : }
187 0 : if (GNUNET_OK !=
188 0 : TALER_amount_cmp_currency (&r->expected_amount,
189 : &rs->details.ok.balance))
190 : {
191 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
192 : "Reserve currency disagreement: exchange `%s' has %s, expected %s\n",
193 : r->exchange_url,
194 : rs->details.ok.balance.currency,
195 : r->expected_amount.currency);
196 0 : free_reserve (r);
197 0 : return;
198 : }
199 0 : if (0 !=
200 0 : TALER_amount_cmp (&r->expected_amount,
201 : &rs->details.ok.balance))
202 : {
203 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
204 : "Reserve initial balance disagreement: exchange `%s' received `%s'\n",
205 : r->exchange_url,
206 : TALER_amount2s (&rs->details.ok.balance));
207 : }
208 0 : qs = TMH_db->activate_reserve (TMH_db->cls,
209 0 : r->instance_id,
210 0 : &r->reserve_pub,
211 : &rs->details.ok.balance);
212 0 : if (qs <= 0)
213 : {
214 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
215 : "Failed to commit reserve activation to database (%d)\n",
216 : (int) qs);
217 : }
218 : else
219 : {
220 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
221 : "Reserve activated with initial balance %s\n",
222 : TALER_amount2s (&rs->details.ok.balance));
223 : }
224 0 : free_reserve (r);
225 : }
226 :
227 :
228 : /**
229 : * Function called with the result of a #TMH_EXCHANGES_find_exchange()
230 : * operation.
231 : *
232 : * @param cls closure
233 : * @param hr HTTP response details
234 : * @param eh handle to the exchange context
235 : * @param payto_uri payto://-URI of the exchange
236 : * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if not available
237 : * @param exchange_trusted true if this exchange is trusted by config
238 : */
239 : static void
240 0 : find_cb (void *cls,
241 : const struct TALER_EXCHANGE_HttpResponse *hr,
242 : struct TALER_EXCHANGE_Handle *eh,
243 : const char *payto_uri,
244 : const struct TALER_Amount *wire_fee,
245 : bool exchange_trusted)
246 : {
247 0 : struct Reserve *r = cls;
248 :
249 0 : r->fo = NULL;
250 0 : if (NULL == eh)
251 : {
252 0 : try_later (r);
253 0 : return;
254 : }
255 0 : r->gh = TALER_EXCHANGE_reserves_get (eh,
256 0 : &r->reserve_pub,
257 : LONGPOLL_DELAY,
258 : &reserve_cb,
259 : r);
260 0 : if (NULL == r->gh)
261 : {
262 0 : try_later (r);
263 0 : return;
264 : }
265 : }
266 :
267 :
268 : /**
269 : * Function called to probe a reserve now.
270 : *
271 : * @param cls a `struct Reserve` to query
272 : */
273 : static void
274 0 : try_now (void *cls)
275 : {
276 0 : struct Reserve *r = cls;
277 :
278 0 : r->tt = NULL;
279 0 : r->fo = TMH_EXCHANGES_find_exchange (r->exchange_url,
280 : NULL,
281 : GNUNET_NO,
282 : &find_cb,
283 : r);
284 0 : if (NULL == r->fo)
285 : {
286 0 : try_later (r);
287 0 : return;
288 : }
289 : }
290 :
291 :
292 : /**
293 : * Function called with information about a reserve that we need
294 : * to check the status from at the exchange to see if/when it has
295 : * been filled (and with what amount).
296 : *
297 : * @param cls closure, NULL
298 : * @param instance_id for which instance is this reserve
299 : * @param exchange_url base URL of the exchange at which the reserve lives
300 : * @param reserve_pub public key of the reserve
301 : * @param expected_amount how much do we expect to see in the reserve
302 : */
303 : static void
304 0 : add_reserve (void *cls,
305 : const char *instance_id,
306 : const char *exchange_url,
307 : const struct TALER_ReservePublicKeyP *reserve_pub,
308 : const struct TALER_Amount *expected_amount)
309 : {
310 : struct Reserve *r;
311 :
312 : (void) cls;
313 0 : r = GNUNET_new (struct Reserve);
314 0 : r->exchange_url = GNUNET_strdup (exchange_url);
315 0 : r->instance_id = GNUNET_strdup (instance_id);
316 0 : r->reserve_pub = *reserve_pub;
317 0 : r->expected_amount = *expected_amount;
318 0 : GNUNET_CONTAINER_DLL_insert (reserves_head,
319 : reserves_tail,
320 : r);
321 0 : try_now (r);
322 0 : }
323 :
324 :
325 : void
326 0 : TMH_RESERVES_init (void)
327 : {
328 0 : TMH_db->lookup_pending_reserves (TMH_db->cls,
329 : &add_reserve,
330 : NULL);
331 0 : }
332 :
333 :
334 : void
335 0 : TMH_RESERVES_check (const char *instance_id,
336 : const char *exchange_url,
337 : const struct TALER_ReservePublicKeyP *reserve_pub,
338 : const struct TALER_Amount *expected_amount)
339 : {
340 0 : add_reserve (NULL,
341 : instance_id,
342 : exchange_url,
343 : reserve_pub,
344 : expected_amount);
345 0 : }
346 :
347 :
348 : void
349 0 : TMH_RESERVES_done (void)
350 : {
351 0 : while (NULL != reserves_head)
352 0 : free_reserve (reserves_head);
353 0 : }
354 :
355 :
356 : /* end of taler-merchant-httpd_reserves.c */
|