test_contracts.c (11161B)
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_contracts.c 18 * @brief tests for the exchangedb functions whose primary table is 19 * `contracts` 20 * @author Christian Grothoff 21 * 22 * Covers #TALER_EXCHANGEDB_insert_contract(), 23 * #TALER_EXCHANGEDB_get_contract() and 24 * #TALER_EXCHANGEDB_get_contract_by_purse(). 25 * 26 * `contracts` references `purse_requests`: the encrypted contract belongs 27 * to a purse. It is fetched either by the purse or by the Diffie-Hellman 28 * public key the client derived, which is why there are two getters. A 29 * second, different contract for the same purse is reported through 30 * @e in_conflict rather than as an error. 31 */ 32 #include "test_common.h" 33 #include "exchange-database/get_contract.h" 34 #include "exchange-database/get_contract_by_purse.h" 35 #include "exchange-database/insert_contract.h" 36 37 38 /** 39 * Build a timestamp from a number of seconds since the epoch. 40 * 41 * @param secs seconds since the epoch 42 * @return the timestamp 43 */ 44 static struct GNUNET_TIME_Timestamp 45 ts (uint64_t secs) 46 { 47 struct GNUNET_TIME_Absolute abs = { 48 .abs_value_us = secs * 1000LLU * 1000LLU 49 }; 50 51 return GNUNET_TIME_absolute_to_timestamp (abs); 52 } 53 54 55 /** 56 * Build an encrypted contract. 57 * 58 * @param seed seed for the key, signature and payload 59 * @param payload contents of the "encrypted" contract 60 * @param[out] econtract set to the contract; free @e econtract with 61 * GNUNET_free() 62 */ 63 static void 64 make_econtract (uint32_t seed, 65 const char *payload, 66 struct TALER_EncryptedContract *econtract) 67 { 68 memset (econtract, 69 0, 70 sizeof (*econtract)); 71 TDB_fill (&econtract->contract_pub, 72 sizeof (econtract->contract_pub), 73 seed); 74 TDB_fill (&econtract->econtract_sig, 75 sizeof (econtract->econtract_sig), 76 seed); 77 econtract->econtract_size = strlen (payload) + 1; 78 econtract->econtract = GNUNET_memdup (payload, 79 econtract->econtract_size); 80 } 81 82 83 /** 84 * Nothing is known while the table is empty, and a contract for a purse 85 * that does not exist is not stored. 86 * 87 * @param pg the database context 88 * @return 0 on success 89 */ 90 static int 91 check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg) 92 { 93 struct TALER_PurseContractPublicKeyP purse_pub; 94 struct TALER_PurseContractSignatureP econtract_sig; 95 struct TALER_ContractDiffiePublicP pub_ckey; 96 struct TALER_EncryptedContract econtract; 97 size_t econtract_size; 98 void *econtract_data = NULL; 99 100 TDB_FILL (purse_pub, 101 1); 102 TDB_FILL (pub_ckey, 103 1); 104 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 105 TALER_EXCHANGEDB_get_contract (pg, 106 &pub_ckey, 107 &purse_pub, 108 &econtract_sig, 109 &econtract_size, 110 &econtract_data)); 111 FAILIF (NULL != econtract_data); 112 memset (&econtract, 113 0, 114 sizeof (econtract)); 115 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 116 TALER_EXCHANGEDB_get_contract_by_purse (pg, 117 &purse_pub, 118 &econtract)); 119 return 0; 120 } 121 122 123 /** 124 * A contract is stored and found by both keys. 125 * 126 * @param pg the database context 127 * @return 0 on success 128 */ 129 static int 130 check_insert_and_lookup (struct TALER_EXCHANGEDB_PostgresContext *pg) 131 { 132 struct TDB_Purse purse; 133 struct TALER_EncryptedContract econtract; 134 struct TALER_EncryptedContract got; 135 struct TALER_PurseContractPublicKeyP got_purse; 136 struct TALER_PurseContractSignatureP got_sig; 137 size_t got_size; 138 void *got_data = NULL; 139 bool in_conflict = true; 140 141 TDB_purse (pg, 142 10, 143 "5", 144 ts (1700000000), 145 &purse); 146 make_econtract (10, 147 "the-contract", 148 &econtract); 149 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 150 TALER_EXCHANGEDB_insert_contract (pg, 151 &purse.purse_pub, 152 &econtract, 153 &in_conflict), 154 GNUNET_free (econtract.econtract)); 155 FAILIF_C (in_conflict, 156 GNUNET_free (econtract.econtract)); 157 FAILIF_C (1 != TDB_count (pg, 158 "FROM contracts"), 159 GNUNET_free (econtract.econtract)); 160 161 /* by the Diffie-Hellman key the client derived */ 162 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 163 TALER_EXCHANGEDB_get_contract (pg, 164 &econtract.contract_pub, 165 &got_purse, 166 &got_sig, 167 &got_size, 168 &got_data), 169 GNUNET_free (econtract.econtract)); 170 FAILIF_C (0 != GNUNET_memcmp (&got_purse, 171 &purse.purse_pub), 172 GNUNET_free (got_data); GNUNET_free (econtract.econtract)); 173 FAILIF_C (0 != GNUNET_memcmp (&got_sig, 174 &econtract.econtract_sig), 175 GNUNET_free (got_data); GNUNET_free (econtract.econtract)); 176 FAILIF_C (got_size != econtract.econtract_size, 177 GNUNET_free (got_data); GNUNET_free (econtract.econtract)); 178 FAILIF_C (0 != memcmp (got_data, 179 econtract.econtract, 180 got_size), 181 GNUNET_free (got_data); GNUNET_free (econtract.econtract)); 182 GNUNET_free (got_data); 183 184 /* and by the purse */ 185 memset (&got, 186 0, 187 sizeof (got)); 188 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 189 TALER_EXCHANGEDB_get_contract_by_purse (pg, 190 &purse.purse_pub, 191 &got), 192 GNUNET_free (econtract.econtract)); 193 FAILIF_C (0 != GNUNET_memcmp (&got.contract_pub, 194 &econtract.contract_pub), 195 GNUNET_free (got.econtract); GNUNET_free (econtract.econtract)); 196 FAILIF_C (got.econtract_size != econtract.econtract_size, 197 GNUNET_free (got.econtract); GNUNET_free (econtract.econtract)); 198 FAILIF_C (0 != memcmp (got.econtract, 199 econtract.econtract, 200 got.econtract_size), 201 GNUNET_free (got.econtract); GNUNET_free (econtract.econtract)); 202 GNUNET_free (got.econtract); 203 204 /* re-inserting the identical contract is a no-op */ 205 in_conflict = true; 206 FAILIF_C (0 > 207 TALER_EXCHANGEDB_insert_contract (pg, 208 &purse.purse_pub, 209 &econtract, 210 &in_conflict), 211 GNUNET_free (econtract.econtract)); 212 FAILIF_C (1 != TDB_count (pg, 213 "FROM contracts"), 214 GNUNET_free (econtract.econtract)); 215 GNUNET_free (econtract.econtract); 216 return 0; 217 } 218 219 220 /** 221 * A different contract for the same purse is a conflict. 222 * 223 * @param pg the database context 224 * @return 0 on success 225 */ 226 static int 227 check_conflict (struct TALER_EXCHANGEDB_PostgresContext *pg) 228 { 229 struct TDB_Purse purse; 230 struct TALER_EncryptedContract econtract; 231 bool in_conflict = false; 232 233 TDB_purse (pg, 234 10, 235 "5", 236 ts (1700000000), 237 &purse); 238 make_econtract (11, 239 "another-contract", 240 &econtract); 241 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 242 TALER_EXCHANGEDB_insert_contract (pg, 243 &purse.purse_pub, 244 &econtract, 245 &in_conflict), 246 GNUNET_free (econtract.econtract)); 247 FAILIF_C (! in_conflict, 248 GNUNET_free (econtract.econtract)); 249 FAILIF_C (1 != TDB_count (pg, 250 "FROM contracts"), 251 GNUNET_free (econtract.econtract)); 252 GNUNET_free (econtract.econtract); 253 return 0; 254 } 255 256 257 /** 258 * A second purse can have its own contract, and the two do not mix. 259 * 260 * @param pg the database context 261 * @return 0 on success 262 */ 263 static int 264 check_second_purse (struct TALER_EXCHANGEDB_PostgresContext *pg) 265 { 266 struct TDB_Purse purse; 267 struct TALER_EncryptedContract econtract; 268 struct TALER_EncryptedContract got; 269 bool in_conflict = true; 270 271 TDB_purse (pg, 272 12, 273 "3", 274 ts (1700000000), 275 &purse); 276 make_econtract (12, 277 "second-contract", 278 &econtract); 279 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 280 TALER_EXCHANGEDB_insert_contract (pg, 281 &purse.purse_pub, 282 &econtract, 283 &in_conflict), 284 GNUNET_free (econtract.econtract)); 285 FAILIF_C (in_conflict, 286 GNUNET_free (econtract.econtract)); 287 FAILIF_C (2 != TDB_count (pg, 288 "FROM contracts"), 289 GNUNET_free (econtract.econtract)); 290 memset (&got, 291 0, 292 sizeof (got)); 293 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 294 TALER_EXCHANGEDB_get_contract_by_purse (pg, 295 &purse.purse_pub, 296 &got), 297 GNUNET_free (econtract.econtract)); 298 FAILIF_C (0 != GNUNET_memcmp (&got.contract_pub, 299 &econtract.contract_pub), 300 GNUNET_free (got.econtract); GNUNET_free (econtract.econtract)); 301 GNUNET_free (got.econtract); 302 GNUNET_free (econtract.econtract); 303 return 0; 304 } 305 306 307 /** 308 * The checks to run, in order. 309 */ 310 static const struct TDB_Test tests[] = { 311 { "contracts-empty", 312 &check_empty }, 313 { "contracts-insert-and-lookup", 314 &check_insert_and_lookup }, 315 { "contracts-conflict", 316 &check_conflict }, 317 { "contracts-second-purse", 318 &check_second_purse }, 319 { NULL, NULL } 320 }; 321 322 323 int 324 main (int argc, 325 char *const *argv) 326 { 327 return TDB_main (argc, 328 argv, 329 "test-contracts", 330 "Tests for the exchangedb `contracts' table", 331 tests); 332 } 333 334 335 /* end of test_contracts.c */