do_insert_known_coins.c (9136B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2022-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/do_insert_known_coins.c 18 * @brief Implementation of the do_insert_known_coins function for Postgres 19 * @author Christian Grothoff 20 * @author Özgür Kesim 21 */ 22 #include "platform.h" 23 #include "taler/taler_error_codes.h" 24 #include "taler/taler_dbevents.h" 25 #include "exchangedb_lib.h" 26 #include "taler/taler_pq_lib.h" 27 #include "exchange-database/do_insert_known_coins.h" 28 #include "helper.h" 29 30 31 /** 32 * Closure for #known_coins_cb(). 33 */ 34 struct KnownCoinsContext 35 { 36 /** 37 * The coins we asked about. 38 */ 39 const struct TALER_CoinPublicInfo *const *coins; 40 41 /** 42 * Where to put the per-coin outcome. 43 */ 44 struct TALER_EXCHANGEDB_CoinKnownResult *results; 45 46 /** 47 * Number of entries in @e coins and @e results. 48 */ 49 unsigned int num_coins; 50 51 /** 52 * Number of rows we have processed so far. 53 */ 54 unsigned int num_rows; 55 56 /** 57 * Set to true if a row could not be processed. 58 */ 59 bool failed; 60 }; 61 62 63 /** 64 * Function called with one row per coin. Fills the 65 * result entry at the coin's input position. 66 * 67 * @param cls a `struct KnownCoinsContext *` 68 * @param result the result 69 * @param num_results number of rows in @a result 70 */ 71 static void 72 known_coins_cb (void *cls, 73 PGresult *result, 74 unsigned int num_results) 75 { 76 struct KnownCoinsContext *kcc = cls; 77 78 for (unsigned int i = 0; i < num_results; i++) 79 { 80 uint64_t idx; 81 bool existed; 82 uint64_t known_coin_id; 83 bool no_known_coin_id; 84 struct TALER_DenominationHashP h_denom_pub; 85 bool no_denom_pub_hash; 86 struct TALER_AgeCommitmentHashP h_age_commitment; 87 bool no_age_commitment_hash; 88 struct GNUNET_PQ_ResultSpec rs[] = { 89 GNUNET_PQ_result_spec_uint64 ("out_idx", 90 &idx), 91 GNUNET_PQ_result_spec_bool ("out_existed", 92 &existed), 93 GNUNET_PQ_result_spec_allow_null ( 94 GNUNET_PQ_result_spec_uint64 ("out_known_coin_id", 95 &known_coin_id), 96 &no_known_coin_id), 97 GNUNET_PQ_result_spec_allow_null ( 98 GNUNET_PQ_result_spec_auto_from_type ("out_denom_pub_hash", 99 &h_denom_pub), 100 &no_denom_pub_hash), 101 GNUNET_PQ_result_spec_allow_null ( 102 GNUNET_PQ_result_spec_auto_from_type ("out_age_commitment_hash", 103 &h_age_commitment), 104 &no_age_commitment_hash), 105 GNUNET_PQ_result_spec_end 106 }; 107 const struct TALER_CoinPublicInfo *coin; 108 struct TALER_EXCHANGEDB_CoinKnownResult *res; 109 110 if (GNUNET_OK != 111 GNUNET_PQ_extract_result (result, 112 rs, 113 i)) 114 { 115 GNUNET_break (0); 116 kcc->failed = true; 117 return; 118 } 119 /* out_idx is the 1-based ordinality of the input arrays */ 120 if ( (0 == idx) || 121 (idx > kcc->num_coins) ) 122 { 123 GNUNET_break (0); 124 kcc->failed = true; 125 return; 126 } 127 coin = kcc->coins[idx - 1]; 128 res = &kcc->results[idx - 1]; 129 if (no_known_coin_id) 130 { 131 /* neither inserted nor found: the denomination is unknown */ 132 GNUNET_break (0); 133 kcc->failed = true; 134 return; 135 } 136 res->known_coin_id = known_coin_id; 137 kcc->num_rows++; 138 if (! existed) 139 { 140 /* The row was inserted by this very statement, which the 141 query cannot see yet: what is stored is what we passed. */ 142 res->status = TALER_EXCHANGEDB_CKS_ADDED; 143 res->h_denom_pub = coin->denom_pub_hash; 144 res->no_age_commitment = coin->no_age_commitment; 145 if (! coin->no_age_commitment) 146 res->h_age_commitment = coin->h_age_commitment; 147 continue; 148 } 149 res->no_age_commitment = no_age_commitment_hash; 150 if (! no_denom_pub_hash) 151 res->h_denom_pub = h_denom_pub; 152 if (! no_age_commitment_hash) 153 res->h_age_commitment = h_age_commitment; 154 if ( (! no_denom_pub_hash) && 155 (0 != GNUNET_memcmp (&h_denom_pub, 156 &coin->denom_pub_hash)) ) 157 { 158 GNUNET_break_op (0); 159 res->status = TALER_EXCHANGEDB_CKS_DENOM_CONFLICT; 160 continue; 161 } 162 if (no_age_commitment_hash != coin->no_age_commitment) 163 { 164 GNUNET_break_op (0); 165 res->status = no_age_commitment_hash 166 ? TALER_EXCHANGEDB_CKS_AGE_CONFLICT_EXPECTED_NULL 167 : TALER_EXCHANGEDB_CKS_AGE_CONFLICT_EXPECTED_NON_NULL; 168 continue; 169 } 170 if ( (! no_age_commitment_hash) && 171 (0 != GNUNET_memcmp (&h_age_commitment, 172 &coin->h_age_commitment)) ) 173 { 174 GNUNET_break_op (0); 175 res->status = TALER_EXCHANGEDB_CKS_AGE_CONFLICT_VALUE_DIFFERS; 176 continue; 177 } 178 res->status = TALER_EXCHANGEDB_CKS_PRESENT; 179 } 180 } 181 182 183 enum GNUNET_DB_QueryStatus 184 TALER_EXCHANGEDB_do_insert_known_coins ( 185 struct TALER_EXCHANGEDB_PostgresContext *pg, 186 unsigned int num_coins, 187 const struct TALER_CoinPublicInfo *const coins[static num_coins], 188 struct TALER_EXCHANGEDB_CoinKnownResult results[static num_coins]) 189 { 190 struct TALER_CoinSpendPublicKeyP coin_pubs[num_coins]; 191 struct TALER_DenominationHashP denom_pub_hashes[num_coins]; 192 bool has_age_commitments[num_coins]; 193 struct TALER_AgeCommitmentHashP age_commitment_hashes[num_coins]; 194 struct TALER_DenominationSignature denom_sigs[num_coins]; 195 struct GNUNET_PQ_QueryParam params[] = { 196 GNUNET_PQ_query_param_array_auto_from_type (num_coins, 197 coin_pubs, 198 pg->conn), 199 GNUNET_PQ_query_param_array_auto_from_type (num_coins, 200 denom_pub_hashes, 201 pg->conn), 202 GNUNET_PQ_query_param_array_bool (num_coins, 203 has_age_commitments, 204 pg->conn), 205 GNUNET_PQ_query_param_array_auto_from_type (num_coins, 206 age_commitment_hashes, 207 pg->conn), 208 TALER_PQ_query_param_array_denom_sig (num_coins, 209 denom_sigs, 210 pg->conn), 211 GNUNET_PQ_query_param_end 212 }; 213 struct KnownCoinsContext kcc = { 214 .coins = coins, 215 .results = results, 216 .num_coins = num_coins 217 }; 218 enum GNUNET_DB_QueryStatus qs; 219 220 GNUNET_assert (0 < num_coins); 221 /* Gather the columns; the arrays cannot carry NULL elements, so coins 222 without age commitment get a zero hash and a false flag. */ 223 for (unsigned int i = 0; i < num_coins; i++) 224 { 225 const struct TALER_CoinPublicInfo *coin = coins[i]; 226 227 coin_pubs[i] = coin->coin_pub; 228 denom_pub_hashes[i] = coin->denom_pub_hash; 229 has_age_commitments[i] = ! coin->no_age_commitment; 230 if (coin->no_age_commitment) 231 memset (&age_commitment_hashes[i], 232 0, 233 sizeof (age_commitment_hashes[i])); 234 else 235 age_commitment_hashes[i] = coin->h_age_commitment; 236 denom_sigs[i] = coin->denom_sig; 237 /* A single INSERT cannot handle the same key twice. */ 238 for (unsigned int j = 0; j < i; j++) 239 { 240 if (0 == GNUNET_memcmp (&coin_pubs[i], 241 &coin_pubs[j])) 242 { 243 GNUNET_break (0); 244 GNUNET_PQ_cleanup_query_params_closures (params); 245 return GNUNET_DB_STATUS_HARD_ERROR; 246 } 247 } 248 } 249 memset (results, 250 0, 251 num_coins * sizeof (*results)); 252 PREPARE (pg, 253 "do_insert_known_coins", 254 "SELECT" 255 " out_idx" 256 ",out_existed" 257 ",out_known_coin_id" 258 ",out_denom_pub_hash" 259 ",out_age_commitment_hash" 260 " FROM exchange_do_insert_known_coins" 261 " ($1, $2, $3, $4, $5);"); 262 qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, 263 "do_insert_known_coins", 264 params, 265 &known_coins_cb, 266 &kcc); 267 GNUNET_PQ_cleanup_query_params_closures (params); 268 if (0 > qs) 269 return qs; 270 if ( (kcc.failed) || 271 (kcc.num_rows != num_coins) ) 272 { 273 /* an unknown denomination or a malformed reply */ 274 GNUNET_break (0); 275 return GNUNET_DB_STATUS_HARD_ERROR; 276 } 277 return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 278 }