Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2026 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU 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 exchangedb/test_coin_deposits.c
18 : * @brief tests for the exchangedb functions whose primary table is
19 : * `coin_deposits`
20 : * @author Christian Grothoff
21 : *
22 : * Covers #TALER_EXCHANGEDB_iterate_coin_deposits_above_serial_id() and
23 : * #TALER_EXCHANGEDB_iterate_deposit_amounts_for_kyc_check().
24 : *
25 : * The rows themselves are written by #TALER_EXCHANGEDB_do_deposit(), whose
26 : * primary table is `batch_deposits` -- TDB_deposit() calls it. What is
27 : * checked here is the two views onto the coins of a deposit: the auditor's
28 : * serial walk and the KYC sum per merchant account.
29 : */
30 : #include "test_common.h"
31 : #include "exchange-database/iterate_coin_deposits_above_serial_id.h"
32 : #include "exchange-database/iterate_deposit_amounts_for_kyc_check.h"
33 :
34 :
35 : /**
36 : * Account the merchants of the checks are paid at.
37 : */
38 : static struct TDB_Account account;
39 :
40 :
41 : /**
42 : * A second account, to show the KYC view is per account.
43 : */
44 : static struct TDB_Account other;
45 :
46 :
47 : /**
48 : * Denomination the checks deposit.
49 : */
50 : static struct TDB_Denom denom;
51 :
52 :
53 : /**
54 : * Build a timestamp from a number of seconds since the epoch.
55 : *
56 : * @param secs seconds since the epoch
57 : * @return the timestamp
58 : */
59 : static struct GNUNET_TIME_Timestamp
60 6 : ts (uint64_t secs)
61 : {
62 6 : struct GNUNET_TIME_Absolute abs = {
63 6 : .abs_value_us = secs * 1000LLU * 1000LLU
64 : };
65 :
66 6 : return GNUNET_TIME_absolute_to_timestamp (abs);
67 : }
68 :
69 :
70 : /**
71 : * Closure for #deposit_cb().
72 : */
73 : struct DepositContext
74 : {
75 : /**
76 : * How many rows did the callback see?
77 : */
78 : unsigned int total;
79 :
80 : /**
81 : * Stop after this many rows; 0 for no limit.
82 : */
83 : unsigned int stop_after;
84 :
85 : /**
86 : * Coin we are looking for, NULL to match nothing.
87 : */
88 : const struct TALER_CoinSpendPublicKeyP *coin_pub;
89 :
90 : /**
91 : * How many times did we see it?
92 : */
93 : unsigned int matched;
94 :
95 : /**
96 : * Amount reported for it.
97 : */
98 : struct TALER_Amount amount;
99 :
100 : /**
101 : * Merchant reported for it.
102 : */
103 : struct TALER_MerchantPublicKeyP merchant_pub;
104 :
105 : /**
106 : * Was it reported as executed?
107 : */
108 : bool done;
109 : };
110 :
111 :
112 : /**
113 : * Callback for #TALER_EXCHANGEDB_iterate_coin_deposits_above_serial_id().
114 : *
115 : * @param cls a `struct DepositContext *`
116 : * @param rowid row of the coin deposit
117 : * @param exchange_timestamp when the deposit was made
118 : * @param deposit details of the deposit
119 : * @param denom_pub denomination of the coin
120 : * @param done whether the deposit was already wired out
121 : * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop
122 : */
123 : static enum GNUNET_GenericReturnValue
124 5 : deposit_cb (void *cls,
125 : uint64_t rowid,
126 : struct GNUNET_TIME_Timestamp exchange_timestamp,
127 : const struct TALER_EXCHANGEDB_Deposit *deposit,
128 : const struct TALER_DenominationPublicKey *denom_pub,
129 : bool done)
130 : {
131 5 : struct DepositContext *ctx = cls;
132 :
133 : (void) rowid;
134 : (void) exchange_timestamp;
135 : (void) denom_pub;
136 5 : ctx->total++;
137 5 : if ( (NULL != ctx->coin_pub) &&
138 4 : (0 == GNUNET_memcmp (&deposit->coin.coin_pub,
139 : ctx->coin_pub)) )
140 : {
141 2 : ctx->matched++;
142 2 : ctx->amount = deposit->amount_with_fee;
143 2 : ctx->merchant_pub = deposit->merchant_pub;
144 2 : ctx->done = done;
145 : }
146 5 : if ( (0 != ctx->stop_after) &&
147 1 : (ctx->total >= ctx->stop_after) )
148 1 : return GNUNET_SYSERR;
149 4 : return GNUNET_OK;
150 : }
151 :
152 :
153 : /**
154 : * Closure for #amount_cb().
155 : */
156 : struct AmountContext
157 : {
158 : /**
159 : * How many amounts did the callback see?
160 : */
161 : unsigned int total;
162 :
163 : /**
164 : * Sum of the whole-unit parts of the amounts seen.
165 : */
166 : uint64_t value_sum;
167 :
168 : /**
169 : * Return this from the callback.
170 : */
171 : enum GNUNET_GenericReturnValue ret;
172 : };
173 :
174 :
175 : /**
176 : * Callback for #TALER_EXCHANGEDB_iterate_deposit_amounts_for_kyc_check().
177 : *
178 : * @param cls a `struct AmountContext *`
179 : * @param amount the deposited amount
180 : * @param date when it was deposited
181 : * @return what @e ret of the closure says
182 : */
183 : static enum GNUNET_GenericReturnValue
184 4 : amount_cb (void *cls,
185 : const struct TALER_Amount *amount,
186 : struct GNUNET_TIME_Absolute date)
187 : {
188 4 : struct AmountContext *ctx = cls;
189 :
190 : (void) date;
191 4 : ctx->total++;
192 4 : ctx->value_sum += amount->value;
193 4 : return ctx->ret;
194 : }
195 :
196 :
197 : /**
198 : * Nothing is reported while the table is empty.
199 : *
200 : * @param pg the database context
201 : * @return 0 on success
202 : */
203 : static int
204 1 : check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg)
205 : {
206 1 : struct DepositContext ctx = { 0 };
207 1 : struct AmountContext actx = { 0 };
208 : struct TALER_NormalizedPaytoHashP h_payto;
209 :
210 1 : TDB_FILL (h_payto,
211 : 1);
212 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
213 : TALER_EXCHANGEDB_iterate_coin_deposits_above_serial_id (
214 : pg,
215 : 0,
216 : &deposit_cb,
217 : &ctx));
218 1 : actx.ret = GNUNET_OK;
219 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
220 : TALER_EXCHANGEDB_iterate_deposit_amounts_for_kyc_check (
221 : pg,
222 : &h_payto,
223 : GNUNET_TIME_UNIT_ZERO_ABS,
224 : &amount_cb,
225 : &actx));
226 1 : FAILIF (0 != ctx.total);
227 1 : FAILIF (0 != actx.total);
228 1 : return 0;
229 : }
230 :
231 :
232 : /**
233 : * A deposited coin is reported with the merchant and amount it went to.
234 : *
235 : * @param pg the database context
236 : * @return 0 on success
237 : */
238 : static int
239 1 : check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg)
240 : {
241 : struct TALER_CoinPublicInfo coin;
242 : struct TDB_Deposit dep;
243 : struct DepositContext ctx;
244 1 : struct TALER_Amount expect = TDB_amount ("1");
245 :
246 1 : TDB_denom (pg,
247 : 10,
248 : "5",
249 : "0.1",
250 : &denom);
251 1 : TDB_account (pg,
252 : 10,
253 : &account);
254 1 : TDB_coin (pg,
255 : &denom,
256 : 20,
257 : &coin,
258 : NULL);
259 1 : TDB_deposit (pg,
260 : &account,
261 : &coin,
262 : 20,
263 : "1",
264 : "0.1",
265 : ts (1600000000),
266 : ts (1600000000),
267 : &dep);
268 1 : memset (&ctx,
269 : 0,
270 : sizeof (ctx));
271 1 : ctx.coin_pub = &coin.coin_pub;
272 1 : FAILIF_C (1 !=
273 : TALER_EXCHANGEDB_iterate_coin_deposits_above_serial_id (
274 : pg,
275 : 0,
276 : &deposit_cb,
277 : &ctx),
278 : TDB_coin_free (&coin));
279 1 : FAILIF_C (1 != ctx.matched,
280 : TDB_coin_free (&coin));
281 1 : FAILIF_C (0 != TALER_amount_cmp (&ctx.amount,
282 : &expect),
283 : TDB_coin_free (&coin));
284 1 : FAILIF_C (0 != GNUNET_memcmp (&ctx.merchant_pub,
285 : &dep.merchant_pub),
286 : TDB_coin_free (&coin));
287 1 : FAILIF_C (ctx.done,
288 : TDB_coin_free (&coin));
289 :
290 : /* the serial bound is exclusive */
291 1 : memset (&ctx,
292 : 0,
293 : sizeof (ctx));
294 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
295 : TALER_EXCHANGEDB_iterate_coin_deposits_above_serial_id (
296 : pg,
297 : 1000,
298 : &deposit_cb,
299 : &ctx),
300 : TDB_coin_free (&coin));
301 1 : FAILIF_C (0 != ctx.total,
302 : TDB_coin_free (&coin));
303 1 : TDB_coin_free (&coin);
304 1 : return 0;
305 : }
306 :
307 :
308 : /**
309 : * The KYC view sums the deposits per merchant account, and honours the
310 : * time limit and the callback's abort.
311 : *
312 : * @param pg the database context
313 : * @return 0 on success
314 : */
315 : static int
316 1 : check_kyc_amounts (struct TALER_EXCHANGEDB_PostgresContext *pg)
317 : {
318 : struct TALER_CoinPublicInfo c2;
319 : struct TALER_CoinPublicInfo c3;
320 : struct TDB_Deposit dep;
321 : struct AmountContext ctx;
322 :
323 1 : TDB_account (pg,
324 : 11,
325 : &other);
326 : /* a second deposit to the same account, and one to another */
327 1 : TDB_coin (pg,
328 : &denom,
329 : 21,
330 : &c2,
331 : NULL);
332 1 : TDB_deposit (pg,
333 : &account,
334 : &c2,
335 : 21,
336 : "2",
337 : "0.1",
338 : ts (1600003600),
339 : ts (1600003600),
340 : &dep);
341 1 : TDB_coin (pg,
342 : &denom,
343 : 22,
344 : &c3,
345 : NULL);
346 1 : TDB_deposit (pg,
347 : &other,
348 : &c3,
349 : 22,
350 : "4",
351 : "0.1",
352 : ts (1600007200),
353 : ts (1600007200),
354 : &dep);
355 1 : TDB_coin_free (&c2);
356 1 : TDB_coin_free (&c3);
357 1 : FAILIF (3 != TDB_count (pg,
358 : "FROM coin_deposits"));
359 :
360 1 : memset (&ctx,
361 : 0,
362 : sizeof (ctx));
363 1 : ctx.ret = GNUNET_OK;
364 1 : FAILIF (2 !=
365 : TALER_EXCHANGEDB_iterate_deposit_amounts_for_kyc_check (
366 : pg,
367 : &account.h_normalized,
368 : GNUNET_TIME_UNIT_ZERO_ABS,
369 : &amount_cb,
370 : &ctx));
371 1 : FAILIF (1 + 2 != ctx.value_sum);
372 :
373 1 : memset (&ctx,
374 : 0,
375 : sizeof (ctx));
376 1 : ctx.ret = GNUNET_OK;
377 1 : FAILIF (1 !=
378 : TALER_EXCHANGEDB_iterate_deposit_amounts_for_kyc_check (
379 : pg,
380 : &other.h_normalized,
381 : GNUNET_TIME_UNIT_ZERO_ABS,
382 : &amount_cb,
383 : &ctx));
384 1 : FAILIF (4 != ctx.value_sum);
385 :
386 : /* a limit in the future hides everything */
387 1 : memset (&ctx,
388 : 0,
389 : sizeof (ctx));
390 1 : ctx.ret = GNUNET_OK;
391 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
392 : TALER_EXCHANGEDB_iterate_deposit_amounts_for_kyc_check (
393 : pg,
394 : &account.h_normalized,
395 : GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_HOURS),
396 : &amount_cb,
397 : &ctx));
398 1 : FAILIF (0 != ctx.total);
399 :
400 : /* a callback that aborts with GNUNET_NO is not an error */
401 1 : memset (&ctx,
402 : 0,
403 : sizeof (ctx));
404 1 : ctx.ret = GNUNET_NO;
405 1 : FAILIF (0 >
406 : TALER_EXCHANGEDB_iterate_deposit_amounts_for_kyc_check (
407 : pg,
408 : &account.h_normalized,
409 : GNUNET_TIME_UNIT_ZERO_ABS,
410 : &amount_cb,
411 : &ctx));
412 1 : FAILIF (1 != ctx.total);
413 1 : return 0;
414 : }
415 :
416 :
417 : /**
418 : * A deposit marked done is reported as such.
419 : *
420 : * @param pg the database context
421 : * @return 0 on success
422 : */
423 : static int
424 1 : check_done_flag (struct TALER_EXCHANGEDB_PostgresContext *pg)
425 : {
426 : struct DepositContext ctx;
427 :
428 1 : FAILIF (GNUNET_OK !=
429 : TDB_exec (pg,
430 : "UPDATE batch_deposits SET done=TRUE;"));
431 1 : memset (&ctx,
432 : 0,
433 : sizeof (ctx));
434 1 : ctx.stop_after = 1;
435 1 : FAILIF (3 !=
436 : TALER_EXCHANGEDB_iterate_coin_deposits_above_serial_id (
437 : pg,
438 : 0,
439 : &deposit_cb,
440 : &ctx));
441 1 : FAILIF (1 != ctx.total);
442 :
443 : {
444 : struct TALER_CoinSpendPublicKeyP coin_pub;
445 :
446 1 : TDB_FILL (coin_pub,
447 : 20);
448 1 : memset (&ctx,
449 : 0,
450 : sizeof (ctx));
451 1 : ctx.coin_pub = &coin_pub;
452 1 : FAILIF (3 !=
453 : TALER_EXCHANGEDB_iterate_coin_deposits_above_serial_id (
454 : pg,
455 : 0,
456 : &deposit_cb,
457 : &ctx));
458 1 : FAILIF (1 != ctx.matched);
459 1 : FAILIF (! ctx.done);
460 : }
461 1 : return 0;
462 : }
463 :
464 :
465 : /**
466 : * The checks to run, in order.
467 : */
468 : static const struct TDB_Test tests[] = {
469 : { "coin-deposits-empty",
470 : &check_empty },
471 : { "coin-deposits-iterate",
472 : &check_iterate },
473 : { "coin-deposits-kyc-amounts",
474 : &check_kyc_amounts },
475 : { "coin-deposits-done-flag",
476 : &check_done_flag },
477 : { NULL, NULL }
478 : };
479 :
480 :
481 : int
482 1 : main (int argc,
483 : char *const *argv)
484 : {
485 : int ret;
486 :
487 1 : ret = TDB_main (argc,
488 : argv,
489 : "test-coin-deposits",
490 : "Tests for the exchangedb `coin_deposits' table",
491 : tests);
492 1 : TDB_account_free (&account);
493 1 : TDB_account_free (&other);
494 1 : TDB_denom_free (&denom);
495 1 : return ret;
496 : }
497 :
498 :
499 : /* end of test_coin_deposits.c */
|