test_purse_deletion.c (11893B)
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_purse_deletion.c 18 * @brief tests for the exchangedb functions whose primary table is 19 * `purse_deletion` 20 * @author Christian Grothoff 21 * 22 * Covers #TALER_EXCHANGEDB_do_purse_delete() and 23 * #TALER_EXCHANGEDB_iterate_all_purse_deletions_above_serial_id(). 24 * 25 * Deleting a purse is only possible while it is undecided: the two 26 * negative answers of do_purse_delete() are "no such purse" and "already 27 * decided", and the checks below produce both. 28 */ 29 #include "test_common.h" 30 #include "exchange-database/do_expire_purse.h" 31 #include "exchange-database/do_purse_delete.h" 32 #include "exchange-database/do_purse_deposit.h" 33 #include "exchange-database/get_purse.h" 34 #include "exchange-database/iterate_all_purse_deletions_above_serial_id.h" 35 36 37 /** 38 * Denomination the checks deposit into purses. 39 */ 40 static struct TDB_Denom denom; 41 42 43 /** 44 * Build a timestamp from a number of seconds since the epoch. 45 * 46 * @param secs seconds since the epoch 47 * @return the timestamp 48 */ 49 static struct GNUNET_TIME_Timestamp 50 ts (uint64_t secs) 51 { 52 struct GNUNET_TIME_Absolute abs = { 53 .abs_value_us = secs * 1000LLU * 1000LLU 54 }; 55 56 return GNUNET_TIME_absolute_to_timestamp (abs); 57 } 58 59 60 /** 61 * Closure for #deletion_cb(). 62 */ 63 struct DeletionContext 64 { 65 /** 66 * How many rows did the callback see? 67 */ 68 unsigned int total; 69 70 /** 71 * Stop after this many rows; 0 for no limit. 72 */ 73 unsigned int stop_after; 74 75 /** 76 * Purse we are looking for, NULL to match nothing. 77 */ 78 const struct TALER_PurseContractPublicKeyP *purse_pub; 79 80 /** 81 * How many times did we see it? 82 */ 83 unsigned int matched; 84 85 /** 86 * Signature reported for it. 87 */ 88 struct TALER_PurseContractSignatureP purse_sig; 89 }; 90 91 92 /** 93 * Callback for 94 * #TALER_EXCHANGEDB_iterate_all_purse_deletions_above_serial_id(). 95 * 96 * @param cls a `struct DeletionContext *` 97 * @param rowid row of the deletion 98 * @param purse_pub the purse that was deleted 99 * @param purse_sig signature affirming the deletion 100 * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop 101 */ 102 static enum GNUNET_GenericReturnValue 103 deletion_cb (void *cls, 104 uint64_t rowid, 105 const struct TALER_PurseContractPublicKeyP *purse_pub, 106 const struct TALER_PurseContractSignatureP *purse_sig) 107 { 108 struct DeletionContext *ctx = cls; 109 110 (void) rowid; 111 ctx->total++; 112 if ( (NULL != ctx->purse_pub) && 113 (0 == GNUNET_memcmp (purse_pub, 114 ctx->purse_pub)) ) 115 { 116 ctx->matched++; 117 ctx->purse_sig = *purse_sig; 118 } 119 if ( (0 != ctx->stop_after) && 120 (ctx->total >= ctx->stop_after) ) 121 return GNUNET_SYSERR; 122 return GNUNET_OK; 123 } 124 125 126 /** 127 * A purse that does not exist cannot be deleted. 128 * 129 * @param pg the database context 130 * @return 0 on success 131 */ 132 static int 133 check_unknown_purse (struct TALER_EXCHANGEDB_PostgresContext *pg) 134 { 135 struct TALER_PurseContractPublicKeyP purse_pub; 136 struct TALER_PurseContractSignatureP purse_sig; 137 struct DeletionContext ctx = { 0 }; 138 bool decided = true; 139 bool found = true; 140 141 TDB_denom (pg, 142 10, 143 "5", 144 "0.1", 145 &denom); 146 TDB_FILL (purse_pub, 147 1); 148 TDB_FILL (purse_sig, 149 1); 150 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 151 TALER_EXCHANGEDB_do_purse_delete (pg, 152 &purse_pub, 153 &purse_sig, 154 &decided, 155 &found)); 156 FAILIF (found); 157 FAILIF (0 != TDB_count (pg, 158 "FROM purse_deletion")); 159 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 160 TALER_EXCHANGEDB_iterate_all_purse_deletions_above_serial_id ( 161 pg, 162 0, 163 &deletion_cb, 164 &ctx)); 165 FAILIF (0 != ctx.total); 166 return 0; 167 } 168 169 170 /** 171 * Deleting an undecided purse records the deletion, refunds the coins in 172 * it and shows up in the purse's status. 173 * 174 * @param pg the database context 175 * @return 0 on success 176 */ 177 static int 178 check_delete (struct TALER_EXCHANGEDB_PostgresContext *pg) 179 { 180 struct TDB_Purse purse; 181 struct TALER_CoinPublicInfo coin; 182 struct TALER_PurseContractSignatureP purse_sig; 183 struct TALER_CoinSpendSignatureP coin_sig; 184 struct TALER_Amount amount = TDB_amount ("1"); 185 struct GNUNET_TIME_Timestamp purse_creation; 186 struct GNUNET_TIME_Timestamp purse_expiration; 187 struct GNUNET_TIME_Timestamp merge_timestamp; 188 struct TALER_Amount target; 189 struct TALER_Amount deposited; 190 struct TALER_PrivateContractHashP h_contract_terms; 191 struct DeletionContext ctx; 192 bool balance_ok; 193 bool too_late; 194 bool conflict; 195 bool decided = true; 196 bool found = false; 197 bool purse_deleted = false; 198 bool purse_refunded = true; 199 char *hex; 200 201 TDB_purse (pg, 202 10, 203 "5", 204 ts (1700000000), 205 &purse); 206 TDB_coin (pg, 207 &denom, 208 20, 209 &coin, 210 NULL); 211 TDB_FILL (coin_sig, 212 20); 213 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 214 TALER_EXCHANGEDB_do_purse_deposit (pg, 215 &purse.purse_pub, 216 &coin.coin_pub, 217 &amount, 218 &coin_sig, 219 &amount, 220 &balance_ok, 221 &too_late, 222 &conflict), 223 TDB_coin_free (&coin)); 224 FAILIF_C (! balance_ok, 225 TDB_coin_free (&coin)); 226 227 TDB_FILL (purse_sig, 228 10); 229 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 230 TALER_EXCHANGEDB_do_purse_delete (pg, 231 &purse.purse_pub, 232 &purse_sig, 233 &decided, 234 &found), 235 TDB_coin_free (&coin)); 236 FAILIF_C (! found, 237 TDB_coin_free (&coin)); 238 FAILIF_C (decided, 239 TDB_coin_free (&coin)); 240 FAILIF_C (1 != TDB_count (pg, 241 "FROM purse_deletion"), 242 TDB_coin_free (&coin)); 243 /* the coin got its money back */ 244 hex = TDB_hex (&coin.coin_pub, 245 sizeof (coin.coin_pub)); 246 FAILIF_C (1 != TDB_count (pg, 247 "FROM known_coins" 248 " WHERE coin_pub=decode('%s','hex')" 249 " AND remaining=ROW(5,0)::taler_amount", 250 hex), 251 GNUNET_free (hex); TDB_coin_free (&coin)); 252 GNUNET_free (hex); 253 TDB_coin_free (&coin); 254 255 /* the purse reports itself as deleted */ 256 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 257 TALER_EXCHANGEDB_get_purse (pg, 258 &purse.purse_pub, 259 &purse_creation, 260 &purse_expiration, 261 &target, 262 &deposited, 263 &h_contract_terms, 264 &merge_timestamp, 265 &purse_deleted, 266 &purse_refunded)); 267 FAILIF (! purse_deleted); 268 269 /* the deletion is reported with the signature it was made with */ 270 memset (&ctx, 271 0, 272 sizeof (ctx)); 273 ctx.purse_pub = &purse.purse_pub; 274 FAILIF (0 >= 275 TALER_EXCHANGEDB_iterate_all_purse_deletions_above_serial_id ( 276 pg, 277 0, 278 &deletion_cb, 279 &ctx)); 280 FAILIF (1 != ctx.matched); 281 FAILIF (0 != GNUNET_memcmp (&ctx.purse_sig, 282 &purse_sig)); 283 284 /* deleting it again finds it already decided */ 285 decided = false; 286 found = false; 287 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 288 TALER_EXCHANGEDB_do_purse_delete (pg, 289 &purse.purse_pub, 290 &purse_sig, 291 &decided, 292 &found)); 293 FAILIF (! found); 294 FAILIF (! decided); 295 FAILIF (1 != TDB_count (pg, 296 "FROM purse_deletion")); 297 return 0; 298 } 299 300 301 /** 302 * A purse that expired first can no longer be deleted. 303 * 304 * @param pg the database context 305 * @return 0 on success 306 */ 307 static int 308 check_already_decided (struct TALER_EXCHANGEDB_PostgresContext *pg) 309 { 310 struct TDB_Purse purse; 311 struct TALER_PurseContractSignatureP purse_sig; 312 struct GNUNET_TIME_Absolute expiration; 313 bool decided = false; 314 bool found = false; 315 316 TDB_purse (pg, 317 11, 318 "5", 319 ts (1600000000), 320 &purse); 321 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 322 TALER_EXCHANGEDB_do_expire_purse (pg, 323 GNUNET_TIME_UNIT_ZERO_ABS, 324 GNUNET_TIME_absolute_get (), 325 &expiration)); 326 TDB_FILL (purse_sig, 327 11); 328 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 329 TALER_EXCHANGEDB_do_purse_delete (pg, 330 &purse.purse_pub, 331 &purse_sig, 332 &decided, 333 &found)); 334 FAILIF (! found); 335 FAILIF (! decided); 336 FAILIF (1 != TDB_count (pg, 337 "FROM purse_deletion")); 338 return 0; 339 } 340 341 342 /** 343 * The iterator's serial bound and abort return behave as documented. 344 * 345 * @param pg the database context 346 * @return 0 on success 347 */ 348 static int 349 check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg) 350 { 351 struct DeletionContext ctx; 352 353 memset (&ctx, 354 0, 355 sizeof (ctx)); 356 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 357 TALER_EXCHANGEDB_iterate_all_purse_deletions_above_serial_id ( 358 pg, 359 1000, 360 &deletion_cb, 361 &ctx)); 362 FAILIF (0 != ctx.total); 363 memset (&ctx, 364 0, 365 sizeof (ctx)); 366 ctx.stop_after = 1; 367 FAILIF (1 != 368 TALER_EXCHANGEDB_iterate_all_purse_deletions_above_serial_id ( 369 pg, 370 0, 371 &deletion_cb, 372 &ctx)); 373 FAILIF (1 != ctx.total); 374 return 0; 375 } 376 377 378 /** 379 * The checks to run, in order. 380 */ 381 static const struct TDB_Test tests[] = { 382 { "purse-deletion-unknown-purse", 383 &check_unknown_purse }, 384 { "purse-deletion-delete", 385 &check_delete }, 386 { "purse-deletion-already-decided", 387 &check_already_decided }, 388 { "purse-deletion-iterate", 389 &check_iterate }, 390 { NULL, NULL } 391 }; 392 393 394 int 395 main (int argc, 396 char *const *argv) 397 { 398 int ret; 399 400 ret = TDB_main (argc, 401 argv, 402 "test-purse-deletion", 403 "Tests for the exchangedb `purse_deletion' table", 404 tests); 405 TDB_denom_free (&denom); 406 return ret; 407 } 408 409 410 /* end of test_purse_deletion.c */