test_coin_deposits.c (12025B)
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 ts (uint64_t secs) 61 { 62 struct GNUNET_TIME_Absolute abs = { 63 .abs_value_us = secs * 1000LLU * 1000LLU 64 }; 65 66 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 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 struct DepositContext *ctx = cls; 132 133 (void) rowid; 134 (void) exchange_timestamp; 135 (void) denom_pub; 136 ctx->total++; 137 if ( (NULL != ctx->coin_pub) && 138 (0 == GNUNET_memcmp (&deposit->coin.coin_pub, 139 ctx->coin_pub)) ) 140 { 141 ctx->matched++; 142 ctx->amount = deposit->amount_with_fee; 143 ctx->merchant_pub = deposit->merchant_pub; 144 ctx->done = done; 145 } 146 if ( (0 != ctx->stop_after) && 147 (ctx->total >= ctx->stop_after) ) 148 return GNUNET_SYSERR; 149 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 amount_cb (void *cls, 185 const struct TALER_Amount *amount, 186 struct GNUNET_TIME_Absolute date) 187 { 188 struct AmountContext *ctx = cls; 189 190 (void) date; 191 ctx->total++; 192 ctx->value_sum += amount->value; 193 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 check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg) 205 { 206 struct DepositContext ctx = { 0 }; 207 struct AmountContext actx = { 0 }; 208 struct TALER_NormalizedPaytoHashP h_payto; 209 210 TDB_FILL (h_payto, 211 1); 212 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 actx.ret = GNUNET_OK; 219 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 FAILIF (0 != ctx.total); 227 FAILIF (0 != actx.total); 228 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 check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg) 240 { 241 struct TALER_CoinPublicInfo coin; 242 struct TDB_Deposit dep; 243 struct DepositContext ctx; 244 struct TALER_Amount expect = TDB_amount ("1"); 245 246 TDB_denom (pg, 247 10, 248 "5", 249 "0.1", 250 &denom); 251 TDB_account (pg, 252 10, 253 &account); 254 TDB_coin (pg, 255 &denom, 256 20, 257 &coin, 258 NULL); 259 TDB_deposit (pg, 260 &account, 261 &coin, 262 20, 263 "1", 264 "0.1", 265 ts (1600000000), 266 ts (1600000000), 267 &dep); 268 memset (&ctx, 269 0, 270 sizeof (ctx)); 271 ctx.coin_pub = &coin.coin_pub; 272 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 FAILIF_C (1 != ctx.matched, 280 TDB_coin_free (&coin)); 281 FAILIF_C (0 != TALER_amount_cmp (&ctx.amount, 282 &expect), 283 TDB_coin_free (&coin)); 284 FAILIF_C (0 != GNUNET_memcmp (&ctx.merchant_pub, 285 &dep.merchant_pub), 286 TDB_coin_free (&coin)); 287 FAILIF_C (ctx.done, 288 TDB_coin_free (&coin)); 289 290 /* the serial bound is exclusive */ 291 memset (&ctx, 292 0, 293 sizeof (ctx)); 294 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 FAILIF_C (0 != ctx.total, 302 TDB_coin_free (&coin)); 303 TDB_coin_free (&coin); 304 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 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 TDB_account (pg, 324 11, 325 &other); 326 /* a second deposit to the same account, and one to another */ 327 TDB_coin (pg, 328 &denom, 329 21, 330 &c2, 331 NULL); 332 TDB_deposit (pg, 333 &account, 334 &c2, 335 21, 336 "2", 337 "0.1", 338 ts (1600003600), 339 ts (1600003600), 340 &dep); 341 TDB_coin (pg, 342 &denom, 343 22, 344 &c3, 345 NULL); 346 TDB_deposit (pg, 347 &other, 348 &c3, 349 22, 350 "4", 351 "0.1", 352 ts (1600007200), 353 ts (1600007200), 354 &dep); 355 TDB_coin_free (&c2); 356 TDB_coin_free (&c3); 357 FAILIF (3 != TDB_count (pg, 358 "FROM coin_deposits")); 359 360 memset (&ctx, 361 0, 362 sizeof (ctx)); 363 ctx.ret = GNUNET_OK; 364 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 FAILIF (1 + 2 != ctx.value_sum); 372 373 memset (&ctx, 374 0, 375 sizeof (ctx)); 376 ctx.ret = GNUNET_OK; 377 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 FAILIF (4 != ctx.value_sum); 385 386 /* a limit in the future hides everything */ 387 memset (&ctx, 388 0, 389 sizeof (ctx)); 390 ctx.ret = GNUNET_OK; 391 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 FAILIF (0 != ctx.total); 399 400 /* a callback that aborts with GNUNET_NO is not an error */ 401 memset (&ctx, 402 0, 403 sizeof (ctx)); 404 ctx.ret = GNUNET_NO; 405 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 FAILIF (1 != ctx.total); 413 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 check_done_flag (struct TALER_EXCHANGEDB_PostgresContext *pg) 425 { 426 struct DepositContext ctx; 427 428 FAILIF (GNUNET_OK != 429 TDB_exec (pg, 430 "UPDATE batch_deposits SET done=TRUE;")); 431 memset (&ctx, 432 0, 433 sizeof (ctx)); 434 ctx.stop_after = 1; 435 FAILIF (3 != 436 TALER_EXCHANGEDB_iterate_coin_deposits_above_serial_id ( 437 pg, 438 0, 439 &deposit_cb, 440 &ctx)); 441 FAILIF (1 != ctx.total); 442 443 { 444 struct TALER_CoinSpendPublicKeyP coin_pub; 445 446 TDB_FILL (coin_pub, 447 20); 448 memset (&ctx, 449 0, 450 sizeof (ctx)); 451 ctx.coin_pub = &coin_pub; 452 FAILIF (3 != 453 TALER_EXCHANGEDB_iterate_coin_deposits_above_serial_id ( 454 pg, 455 0, 456 &deposit_cb, 457 &ctx)); 458 FAILIF (1 != ctx.matched); 459 FAILIF (! ctx.done); 460 } 461 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 main (int argc, 483 char *const *argv) 484 { 485 int ret; 486 487 ret = TDB_main (argc, 488 argv, 489 "test-coin-deposits", 490 "Tests for the exchangedb `coin_deposits' table", 491 tests); 492 TDB_account_free (&account); 493 TDB_account_free (&other); 494 TDB_denom_free (&denom); 495 return ret; 496 } 497 498 499 /* end of test_coin_deposits.c */