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_known_coins.c
18 : * @brief tests for the exchangedb functions whose primary table is
19 : * `known_coins`
20 : * @author Christian Grothoff
21 : *
22 : * Covers #TALER_EXCHANGEDB_do_insert_known_coin(),
23 : * #TALER_EXCHANGEDB_get_known_coin(),
24 : * #TALER_EXCHANGEDB_get_coin_denomination(),
25 : * #TALER_EXCHANGEDB_get_signature_for_known_coin() and
26 : * #TALER_EXCHANGEDB_get_count_known_coins().
27 : *
28 : * `known_coins` references `denominations`, so a denomination is created
29 : * first with TDB_denom(). do_insert_known_coin() is the interesting one:
30 : * it is idempotent, but only for a coin that comes back with the *same*
31 : * denomination and age commitment -- the conflicting cases are what its
32 : * negative status codes are for.
33 : */
34 : #include "test_common.h"
35 : #include "exchange-database/do_insert_known_coin.h"
36 : #include "exchange-database/get_known_coin.h"
37 : #include "exchange-database/get_coin_denomination.h"
38 : #include "exchange-database/get_signature_for_known_coin.h"
39 : #include "exchange-database/get_count_known_coins.h"
40 :
41 :
42 : /**
43 : * Nothing is known about a coin that was never inserted.
44 : *
45 : * @param pg the database context
46 : * @return 0 on success
47 : */
48 : static int
49 1 : check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg)
50 : {
51 : struct TALER_CoinSpendPublicKeyP coin_pub;
52 : struct TALER_CoinPublicInfo info;
53 : struct TALER_DenominationHashP h_denom_pub;
54 1 : struct TALER_DenominationPublicKey denom_pub = { 0 };
55 1 : struct TALER_DenominationSignature denom_sig = { 0 };
56 : uint64_t known_coin_id;
57 :
58 1 : TDB_FILL (coin_pub,
59 : 1);
60 1 : TDB_FILL (h_denom_pub,
61 : 1);
62 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
63 : TALER_EXCHANGEDB_get_known_coin (pg,
64 : &coin_pub,
65 : &info));
66 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
67 : TALER_EXCHANGEDB_get_coin_denomination (pg,
68 : &coin_pub,
69 : &known_coin_id,
70 : &h_denom_pub));
71 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
72 : TALER_EXCHANGEDB_get_signature_for_known_coin (pg,
73 : &coin_pub,
74 : &denom_pub,
75 : &denom_sig));
76 1 : FAILIF (NULL != denom_pub.bsign_pub_key);
77 1 : FAILIF (NULL != denom_sig.unblinded_sig);
78 : /* a denomination that does not exist has no coins */
79 1 : FAILIF (0 != TALER_EXCHANGEDB_get_count_known_coins (pg,
80 : &h_denom_pub));
81 1 : return 0;
82 : }
83 :
84 :
85 : /**
86 : * A coin of an unknown denomination cannot be made known.
87 : *
88 : * @param pg the database context
89 : * @return 0 on success
90 : */
91 : static int
92 1 : check_unknown_denomination (struct TALER_EXCHANGEDB_PostgresContext *pg)
93 : {
94 : struct TALER_CoinPublicInfo coin;
95 : struct TALER_DenominationHashP dh;
96 : struct TALER_AgeCommitmentHashP hac;
97 : uint64_t known_coin_id;
98 :
99 1 : memset (&coin,
100 : 0,
101 : sizeof (coin));
102 1 : TDB_FILL (coin.coin_pub,
103 : 2);
104 1 : TDB_FILL (coin.denom_pub_hash,
105 : 2);
106 1 : coin.no_age_commitment = true;
107 1 : TDB_denom_sig (2,
108 : &coin.denom_sig);
109 : /* the "dd" CTE finds no denomination, so nothing is inserted and the
110 : UNION's second branch finds no coin either */
111 1 : FAILIF_C (TALER_EXCHANGEDB_CKS_HARD_FAIL !=
112 : TALER_EXCHANGEDB_do_insert_known_coin (pg,
113 : &coin,
114 : &known_coin_id,
115 : &dh,
116 : &hac),
117 : TALER_denom_sig_free (&coin.denom_sig));
118 1 : TALER_denom_sig_free (&coin.denom_sig);
119 1 : FAILIF (0 != TDB_count (pg,
120 : "FROM known_coins"));
121 1 : return 0;
122 : }
123 :
124 :
125 : /**
126 : * Making a coin known stores it, and every lookup finds it.
127 : *
128 : * @param pg the database context
129 : * @return 0 on success
130 : */
131 : static int
132 1 : check_insert_and_lookup (struct TALER_EXCHANGEDB_PostgresContext *pg)
133 : {
134 : struct TDB_Denom denom;
135 : struct TALER_CoinPublicInfo coin;
136 : struct TALER_CoinPublicInfo got;
137 : struct TALER_DenominationHashP h_denom_pub;
138 1 : struct TALER_DenominationPublicKey denom_pub = { 0 };
139 1 : struct TALER_DenominationSignature denom_sig = { 0 };
140 1 : uint64_t known_coin_id = 0;
141 1 : uint64_t id2 = 0;
142 :
143 1 : TDB_denom (pg,
144 : 10,
145 : "5",
146 : "0.1",
147 : &denom);
148 1 : TDB_coin (pg,
149 : &denom,
150 : 20,
151 : &coin,
152 : &known_coin_id);
153 1 : FAILIF_C (0 == known_coin_id,
154 : TDB_coin_free (&coin); TDB_denom_free (&denom));
155 :
156 1 : memset (&got,
157 : 0,
158 : sizeof (got));
159 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
160 : TALER_EXCHANGEDB_get_known_coin (pg,
161 : &coin.coin_pub,
162 : &got),
163 : TDB_coin_free (&coin); TDB_denom_free (&denom));
164 1 : FAILIF_C (0 != GNUNET_memcmp (&got.denom_pub_hash,
165 : &denom.h_denom_pub),
166 : TALER_denom_sig_free (&got.denom_sig);
167 : TDB_coin_free (&coin); TDB_denom_free (&denom));
168 1 : FAILIF_C (! got.no_age_commitment,
169 : TALER_denom_sig_free (&got.denom_sig);
170 : TDB_coin_free (&coin); TDB_denom_free (&denom));
171 1 : FAILIF_C (0 != TALER_denom_sig_cmp (&got.denom_sig,
172 : &coin.denom_sig),
173 : TALER_denom_sig_free (&got.denom_sig);
174 : TDB_coin_free (&coin); TDB_denom_free (&denom));
175 1 : TALER_denom_sig_free (&got.denom_sig);
176 :
177 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
178 : TALER_EXCHANGEDB_get_coin_denomination (pg,
179 : &coin.coin_pub,
180 : &id2,
181 : &h_denom_pub),
182 : TDB_coin_free (&coin); TDB_denom_free (&denom));
183 1 : FAILIF_C (id2 != known_coin_id,
184 : TDB_coin_free (&coin); TDB_denom_free (&denom));
185 1 : FAILIF_C (0 != GNUNET_memcmp (&h_denom_pub,
186 : &denom.h_denom_pub),
187 : TDB_coin_free (&coin); TDB_denom_free (&denom));
188 :
189 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
190 : TALER_EXCHANGEDB_get_signature_for_known_coin (pg,
191 : &coin.coin_pub,
192 : &denom_pub,
193 : &denom_sig),
194 : TDB_coin_free (&coin); TDB_denom_free (&denom));
195 1 : FAILIF_C (0 != TALER_denom_pub_cmp (&denom_pub,
196 : &denom.pub),
197 : TALER_denom_pub_free (&denom_pub);
198 : TALER_denom_sig_free (&denom_sig);
199 : TDB_coin_free (&coin); TDB_denom_free (&denom));
200 1 : FAILIF_C (0 != TALER_denom_sig_cmp (&denom_sig,
201 : &coin.denom_sig),
202 : TALER_denom_pub_free (&denom_pub);
203 : TALER_denom_sig_free (&denom_sig);
204 : TDB_coin_free (&coin); TDB_denom_free (&denom));
205 1 : TALER_denom_pub_free (&denom_pub);
206 1 : TALER_denom_sig_free (&denom_sig);
207 :
208 1 : FAILIF_C (1 != TALER_EXCHANGEDB_get_count_known_coins (pg,
209 : &denom.h_denom_pub),
210 : TDB_coin_free (&coin); TDB_denom_free (&denom));
211 1 : TDB_coin_free (&coin);
212 1 : TDB_denom_free (&denom);
213 1 : return 0;
214 : }
215 :
216 :
217 : /**
218 : * Re-inserting the same coin is a no-op; re-inserting it under a different
219 : * denomination or with an age commitment is a conflict.
220 : *
221 : * @param pg the database context
222 : * @return 0 on success
223 : */
224 : static int
225 1 : check_conflicts (struct TALER_EXCHANGEDB_PostgresContext *pg)
226 : {
227 : struct TDB_Denom denom;
228 : struct TDB_Denom other;
229 : struct TALER_CoinPublicInfo coin;
230 : struct TALER_DenominationHashP dh;
231 : struct TALER_AgeCommitmentHashP hac;
232 1 : uint64_t known_coin_id = 0;
233 1 : uint64_t id2 = 0;
234 :
235 1 : TDB_denom (pg,
236 : 10,
237 : "5",
238 : "0.1",
239 : &denom);
240 1 : TDB_denom (pg,
241 : 11,
242 : "5",
243 : "0.1",
244 : &other);
245 1 : memset (&coin,
246 : 0,
247 : sizeof (coin));
248 1 : TDB_FILL (coin.coin_pub,
249 : 20);
250 1 : coin.denom_pub_hash = denom.h_denom_pub;
251 1 : coin.no_age_commitment = true;
252 1 : TDB_denom_sig (20,
253 : &coin.denom_sig);
254 :
255 : /* the coin from the previous check: already present, no conflict */
256 1 : FAILIF_C (TALER_EXCHANGEDB_CKS_PRESENT !=
257 : TALER_EXCHANGEDB_do_insert_known_coin (pg,
258 : &coin,
259 : &known_coin_id,
260 : &dh,
261 : &hac),
262 : TALER_denom_sig_free (&coin.denom_sig);
263 : TDB_denom_free (&denom); TDB_denom_free (&other));
264 1 : FAILIF_C (0 == known_coin_id,
265 : TALER_denom_sig_free (&coin.denom_sig);
266 : TDB_denom_free (&denom); TDB_denom_free (&other));
267 :
268 : /* same coin key, different denomination: conflict, and the stored
269 : denomination is handed back so the caller can report it */
270 1 : coin.denom_pub_hash = other.h_denom_pub;
271 1 : FAILIF_C (TALER_EXCHANGEDB_CKS_DENOM_CONFLICT !=
272 : TALER_EXCHANGEDB_do_insert_known_coin (pg,
273 : &coin,
274 : &id2,
275 : &dh,
276 : &hac),
277 : TALER_denom_sig_free (&coin.denom_sig);
278 : TDB_denom_free (&denom); TDB_denom_free (&other));
279 1 : FAILIF_C (0 != GNUNET_memcmp (&dh,
280 : &denom.h_denom_pub),
281 : TALER_denom_sig_free (&coin.denom_sig);
282 : TDB_denom_free (&denom); TDB_denom_free (&other));
283 :
284 : /* same coin key and denomination, but now with an age commitment where
285 : the stored row has none: the caller should have passed NULL */
286 1 : coin.denom_pub_hash = denom.h_denom_pub;
287 1 : coin.no_age_commitment = false;
288 1 : TDB_FILL (coin.h_age_commitment,
289 : 21);
290 1 : FAILIF_C (TALER_EXCHANGEDB_CKS_AGE_CONFLICT_EXPECTED_NULL !=
291 : TALER_EXCHANGEDB_do_insert_known_coin (pg,
292 : &coin,
293 : &id2,
294 : &dh,
295 : &hac),
296 : TALER_denom_sig_free (&coin.denom_sig);
297 : TDB_denom_free (&denom); TDB_denom_free (&other));
298 1 : TALER_denom_sig_free (&coin.denom_sig);
299 :
300 : /* the conflicting attempts did not add anything */
301 1 : FAILIF_C (1 != TALER_EXCHANGEDB_get_count_known_coins (pg,
302 : &denom.h_denom_pub),
303 : TDB_denom_free (&denom); TDB_denom_free (&other));
304 1 : FAILIF_C (0 != TALER_EXCHANGEDB_get_count_known_coins (pg,
305 : &other.h_denom_pub),
306 : TDB_denom_free (&denom); TDB_denom_free (&other));
307 1 : TDB_denom_free (&denom);
308 1 : TDB_denom_free (&other);
309 1 : return 0;
310 : }
311 :
312 :
313 : /**
314 : * A coin with an age commitment round-trips, and the mirror-image
315 : * conflict is reported.
316 : *
317 : * @param pg the database context
318 : * @return 0 on success
319 : */
320 : static int
321 1 : check_age_commitment (struct TALER_EXCHANGEDB_PostgresContext *pg)
322 : {
323 : struct TDB_Denom denom;
324 : struct TALER_CoinPublicInfo coin;
325 : struct TALER_CoinPublicInfo got;
326 : struct TALER_DenominationHashP dh;
327 : struct TALER_AgeCommitmentHashP hac;
328 1 : uint64_t known_coin_id = 0;
329 :
330 1 : TDB_denom (pg,
331 : 10,
332 : "5",
333 : "0.1",
334 : &denom);
335 1 : memset (&coin,
336 : 0,
337 : sizeof (coin));
338 1 : TDB_FILL (coin.coin_pub,
339 : 30);
340 1 : coin.denom_pub_hash = denom.h_denom_pub;
341 1 : coin.no_age_commitment = false;
342 1 : TDB_FILL (coin.h_age_commitment,
343 : 31);
344 1 : TDB_denom_sig (30,
345 : &coin.denom_sig);
346 1 : FAILIF_C (TALER_EXCHANGEDB_CKS_ADDED !=
347 : TALER_EXCHANGEDB_do_insert_known_coin (pg,
348 : &coin,
349 : &known_coin_id,
350 : &dh,
351 : &hac),
352 : TALER_denom_sig_free (&coin.denom_sig);
353 : TDB_denom_free (&denom));
354 1 : memset (&got,
355 : 0,
356 : sizeof (got));
357 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
358 : TALER_EXCHANGEDB_get_known_coin (pg,
359 : &coin.coin_pub,
360 : &got),
361 : TALER_denom_sig_free (&coin.denom_sig);
362 : TDB_denom_free (&denom));
363 1 : FAILIF_C (got.no_age_commitment,
364 : TALER_denom_sig_free (&got.denom_sig);
365 : TALER_denom_sig_free (&coin.denom_sig);
366 : TDB_denom_free (&denom));
367 1 : FAILIF_C (0 != GNUNET_memcmp (&got.h_age_commitment,
368 : &coin.h_age_commitment),
369 : TALER_denom_sig_free (&got.denom_sig);
370 : TALER_denom_sig_free (&coin.denom_sig);
371 : TDB_denom_free (&denom));
372 1 : TALER_denom_sig_free (&got.denom_sig);
373 :
374 : /* a different age commitment for the same coin is a conflict... */
375 1 : TDB_FILL (coin.h_age_commitment,
376 : 32);
377 1 : FAILIF_C (TALER_EXCHANGEDB_CKS_AGE_CONFLICT_VALUE_DIFFERS !=
378 : TALER_EXCHANGEDB_do_insert_known_coin (pg,
379 : &coin,
380 : &known_coin_id,
381 : &dh,
382 : &hac),
383 : TALER_denom_sig_free (&coin.denom_sig);
384 : TDB_denom_free (&denom));
385 : /* ...and so is no age commitment at all, where one is on file */
386 1 : coin.no_age_commitment = true;
387 1 : FAILIF_C (TALER_EXCHANGEDB_CKS_AGE_CONFLICT_EXPECTED_NON_NULL !=
388 : TALER_EXCHANGEDB_do_insert_known_coin (pg,
389 : &coin,
390 : &known_coin_id,
391 : &dh,
392 : &hac),
393 : TALER_denom_sig_free (&coin.denom_sig);
394 : TDB_denom_free (&denom));
395 1 : TALER_denom_sig_free (&coin.denom_sig);
396 :
397 : /* two coins of this denomination by now */
398 1 : FAILIF_C (2 != TALER_EXCHANGEDB_get_count_known_coins (pg,
399 : &denom.h_denom_pub),
400 : TDB_denom_free (&denom));
401 1 : TDB_denom_free (&denom);
402 1 : return 0;
403 : }
404 :
405 :
406 : /**
407 : * The checks to run, in order.
408 : */
409 : static const struct TDB_Test tests[] = {
410 : { "known-coins-empty",
411 : &check_empty },
412 : { "known-coins-unknown-denomination",
413 : &check_unknown_denomination },
414 : { "known-coins-insert-and-lookup",
415 : &check_insert_and_lookup },
416 : { "known-coins-conflicts",
417 : &check_conflicts },
418 : { "known-coins-age-commitment",
419 : &check_age_commitment },
420 : { NULL, NULL }
421 : };
422 :
423 :
424 : int
425 1 : main (int argc,
426 : char *const *argv)
427 : {
428 1 : return TDB_main (argc,
429 : argv,
430 : "test-known-coins",
431 : "Tests for the exchangedb `known_coins' table",
432 : tests);
433 : }
434 :
435 :
436 : /* end of test_known_coins.c */
|