testing_api_cmd_block_reconcile.c (13973B)
1 /* 2 This file is part of SYNC 3 Copyright (C) 2026 Taler Systems SA 4 5 SYNC is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as 7 published by the Free Software Foundation; either version 3, or 8 (at your option) any later version. 9 10 SYNC is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public 16 License along with SYNC; see the file COPYING. If not, see 17 <http://www.gnu.org/licenses/> 18 */ 19 /** 20 * @file testing/testing_api_cmd_block_reconcile.c 21 * @brief command to fetch and verify the invertible bloom filter of an 22 * account from the sync backend service (DD 92) 23 * @author Iván Ávalos 24 */ 25 #include "platform.h" 26 #include "sync/sync_service.h" 27 #include "sync/sync_signatures.h" 28 #include "sync/sync_ibf.h" 29 #include "sync/sync_testing_lib.h" 30 #include <taler/taler_util.h> 31 #include <taler/taler_testing_lib.h> 32 #include <gnunet/gnunet_curl_lib.h> 33 34 35 /** 36 * Maximum number of entries in the local set and in the expected 37 * differences. 38 */ 39 #define MAX_RECONCILE_ENTRIES 256 40 41 42 /** 43 * State for a "block reconcile" CMD. 44 */ 45 struct BlockReconcileState 46 { 47 48 /** 49 * URL of the sync backend. 50 */ 51 const char *sync_url; 52 53 /** 54 * Reference to a command that provides the account public key. 55 */ 56 const char *account_reference; 57 58 /** 59 * Account public key to query. 60 */ 61 struct SYNC_AccountPublicKeyP account_pub; 62 63 /** 64 * Optional mixing salt to request (NULL for none). 65 */ 66 uint32_t *prefix; 67 68 /** 69 * Expected HTTP status code. 70 */ 71 unsigned int http_status; 72 73 /** 74 * Expected number of blocks the filter must report. 75 */ 76 uint32_t expected_total_blocks; 77 78 /** 79 * The interpreter state. 80 */ 81 struct TALER_TESTING_Interpreter *is; 82 83 /** 84 * Handle for the library block reconcile operation. 85 */ 86 struct SYNC_ReconcileOperation *ro; 87 88 /** 89 * Number of blocks the wallet holds locally. 90 */ 91 unsigned int num_local; 92 93 /** 94 * Nonces of the locally held blocks (array of @e num_local). 95 */ 96 struct SYNC_BlockNonce local_nonces[MAX_RECONCILE_ENTRIES]; 97 98 /** 99 * Hashes of the locally held blocks (array of @e num_local). 100 */ 101 struct GNUNET_HashCode local_hashes[MAX_RECONCILE_ENTRIES]; 102 103 /** 104 * Number of blocks expected to be missing locally (server-only). 105 */ 106 unsigned int num_missing; 107 108 /** 109 * Nonces of the blocks expected missing (array of @e num_missing). 110 */ 111 struct SYNC_BlockNonce missing_nonces[MAX_RECONCILE_ENTRIES]; 112 113 /** 114 * Hashes of the blocks expected missing (array of @e num_missing). 115 */ 116 struct GNUNET_HashCode missing_hashes[MAX_RECONCILE_ENTRIES]; 117 118 /** 119 * Number of blocks expected to be stale locally (local-only). 120 */ 121 unsigned int num_stale; 122 123 /** 124 * Nonces of the blocks expected stale (array of @e num_stale). 125 */ 126 struct SYNC_BlockNonce stale_nonces[MAX_RECONCILE_ENTRIES]; 127 128 /** 129 * Hashes of the blocks expected stale (array of @e num_stale). 130 */ 131 struct GNUNET_HashCode stale_hashes[MAX_RECONCILE_ENTRIES]; 132 133 }; 134 135 136 /** 137 * Look up a decoded element in the expected arrays. 138 * 139 * @param key element to find (nonce || hash) 140 * @param num number of entries in @a nonces and @a hashes 141 * @param nonces expected nonces 142 * @param hashes expected hashes 143 * @param[out] used set to true when found (for duplicate checks) 144 * @return #GNUNET_YES if found, #GNUNET_NO if not 145 */ 146 static enum GNUNET_GenericReturnValue 147 match_expected (const struct SYNC_IBFKey *key, 148 unsigned int num, 149 const struct SYNC_BlockNonce *nonces, 150 const struct GNUNET_HashCode *hashes, 151 bool *used) 152 { 153 for (unsigned int i = 0; i < num; i++) 154 { 155 if (used[i]) 156 continue; 157 if ( (0 == memcmp (key->nonce, 158 &nonces[i], 159 sizeof (key->nonce))) && 160 (0 == memcmp (key->hash, 161 &hashes[i], 162 sizeof (key->hash))) ) 163 { 164 used[i] = true; 165 return GNUNET_YES; 166 } 167 } 168 return GNUNET_NO; 169 } 170 171 172 /** 173 * Function called when we're done processing the block reconcile. 174 * 175 * @param cls the `struct BlockReconcileState` 176 * @param rd details about the reconcile operation 177 */ 178 static void 179 handle_reconcile_finished (void *cls, 180 const struct SYNC_ReconcileDetails *rd) 181 { 182 struct BlockReconcileState *brs = cls; 183 struct SYNC_IBF *server_ibf; 184 struct SYNC_IBF *local_ibf; 185 bool used_missing[MAX_RECONCILE_ENTRIES] = { false }; 186 bool used_stale[MAX_RECONCILE_ENTRIES] = { false }; 187 188 brs->ro = NULL; 189 if (rd->http_status != brs->http_status) 190 { 191 TALER_TESTING_unexpected_status (brs->is, 192 rd->http_status, 193 brs->http_status); 194 return; 195 } 196 if (MHD_HTTP_OK != rd->http_status) 197 { 198 /* error case: nothing more to check */ 199 TALER_TESTING_interpreter_next (brs->is); 200 return; 201 } 202 203 if (rd->total_blocks != brs->expected_total_blocks) 204 { 205 GNUNET_break_op (0); 206 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 207 "Expected %u blocks in reconcile, got %u\n", 208 (unsigned int) brs->expected_total_blocks, 209 (unsigned int) rd->total_blocks); 210 TALER_TESTING_interpreter_fail (brs->is); 211 return; 212 } 213 if ( (NULL != brs->prefix) && 214 (*brs->prefix != rd->prefix) ) 215 { 216 GNUNET_break_op (0); 217 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 218 "Reconcile used prefix %u, expected %u\n", 219 (unsigned int) rd->prefix, 220 (unsigned int) *brs->prefix); 221 TALER_TESTING_interpreter_fail (brs->is); 222 return; 223 } 224 225 server_ibf = SYNC_ibf_deserialize (rd->data, 226 rd->bucket_count, 227 rd->k, 228 rd->prefix); 229 if (NULL == server_ibf) 230 { 231 GNUNET_break_op (0); 232 TALER_TESTING_interpreter_fail (brs->is); 233 return; 234 } 235 /* Rebuild the wallet's side of the reconciliation, as it would: the 236 local filter over the blocks it holds, subtracted from the 237 server's, enumerates exactly the difference. */ 238 local_ibf = SYNC_ibf_create (rd->bucket_count, 239 rd->k, 240 rd->prefix); 241 if (NULL == local_ibf) 242 { 243 GNUNET_break_op (0); 244 SYNC_ibf_destroy (server_ibf); 245 TALER_TESTING_interpreter_fail (brs->is); 246 return; 247 } 248 for (unsigned int i = 0; i < brs->num_local; i++) 249 { 250 struct SYNC_IBFKey key; 251 252 memcpy (key.nonce, 253 &brs->local_nonces[i], 254 sizeof (key.nonce)); 255 memcpy (key.hash, 256 &brs->local_hashes[i], 257 sizeof (key.hash)); 258 SYNC_ibf_insert (local_ibf, 259 &key); 260 } 261 SYNC_ibf_subtract (server_ibf, 262 local_ibf); 263 { 264 bool mismatch = false; 265 unsigned int got_missing = 0; 266 unsigned int got_stale = 0; 267 268 for (;;) 269 { 270 enum GNUNET_GenericReturnValue res; 271 struct SYNC_IBFKey key; 272 int16_t side; 273 274 res = SYNC_ibf_decode (server_ibf, 275 &side, 276 &key); 277 if (GNUNET_YES != res) 278 { 279 if (GNUNET_NO != res) 280 { 281 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 282 "Reconcile filter cannot be decoded (difference" 283 " too large for its size)\n"); 284 mismatch = true; 285 } 286 break; 287 } 288 if (1 == side) 289 { 290 if (GNUNET_YES != 291 match_expected (&key, 292 brs->num_missing, 293 brs->missing_nonces, 294 brs->missing_hashes, 295 used_missing)) 296 { 297 GNUNET_break_op (0); 298 mismatch = true; 299 break; 300 } 301 got_missing++; 302 } 303 else 304 { 305 if (GNUNET_YES != 306 match_expected (&key, 307 brs->num_stale, 308 brs->stale_nonces, 309 brs->stale_hashes, 310 used_stale)) 311 { 312 GNUNET_break_op (0); 313 mismatch = true; 314 break; 315 } 316 got_stale++; 317 } 318 } 319 if ( (! mismatch) && 320 (got_missing != brs->num_missing) ) 321 { 322 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 323 "Reconcile decoded %u missing blocks, expected %u\n", 324 got_missing, 325 brs->num_missing); 326 mismatch = true; 327 } 328 if ( (! mismatch) && 329 (got_stale != brs->num_stale) ) 330 { 331 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 332 "Reconcile decoded %u stale blocks, expected %u\n", 333 got_stale, 334 brs->num_stale); 335 mismatch = true; 336 } 337 if (mismatch) 338 { 339 GNUNET_break_op (0); 340 TALER_TESTING_interpreter_fail (brs->is); 341 SYNC_ibf_destroy (local_ibf); 342 SYNC_ibf_destroy (server_ibf); 343 return; 344 } 345 } 346 SYNC_ibf_destroy (local_ibf); 347 SYNC_ibf_destroy (server_ibf); 348 TALER_TESTING_interpreter_next (brs->is); 349 } 350 351 352 /** 353 * Run a "block reconcile" CMD. 354 * 355 * @param cls closure. 356 * @param cmd command currently being run. 357 * @param is interpreter state. 358 */ 359 static void 360 reconcile_run (void *cls, 361 const struct TALER_TESTING_Command *cmd, 362 struct TALER_TESTING_Interpreter *is) 363 { 364 struct BlockReconcileState *brs = cls; 365 366 (void) cmd; 367 brs->is = is; 368 369 if (NULL != brs->account_reference) 370 { 371 const struct TALER_TESTING_Command *ref; 372 373 ref = TALER_TESTING_interpreter_lookup_command (is, 374 brs->account_reference); 375 if (NULL == ref) 376 { 377 GNUNET_break (0); 378 goto fail; 379 } 380 { 381 const struct SYNC_AccountPublicKeyP *pub; 382 383 if (GNUNET_OK != 384 TALER_TESTING_get_trait_sync_account_pub (ref, 385 &pub)) 386 goto fail; 387 brs->account_pub = *pub; 388 } 389 } 390 else 391 goto fail; 392 393 brs->ro = SYNC_block_reconcile ( 394 TALER_TESTING_interpreter_get_context (is), 395 brs->sync_url, 396 &brs->account_pub, 397 brs->prefix, 398 &handle_reconcile_finished, 399 brs); 400 if (NULL == brs->ro) 401 goto fail; 402 403 return; 404 405 fail: 406 GNUNET_break (0); 407 TALER_TESTING_interpreter_fail (is); 408 } 409 410 411 /** 412 * Free the state of a "block reconcile" CMD, and possibly 413 * cancel it if it did not complete. 414 * 415 * @param cls closure. 416 * @param cmd command being freed. 417 */ 418 static void 419 reconcile_cleanup (void *cls, 420 const struct TALER_TESTING_Command *cmd) 421 { 422 struct BlockReconcileState *brs = cls; 423 424 if (NULL != brs->ro) 425 { 426 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 427 "Command '%s' did not complete (block reconcile)\n", 428 cmd->label); 429 SYNC_block_reconcile_cancel (brs->ro); 430 brs->ro = NULL; 431 } 432 GNUNET_free (brs->prefix); 433 GNUNET_free (brs); 434 } 435 436 437 struct TALER_TESTING_Command 438 SYNC_TESTING_cmd_block_reconcile (const char *label, 439 const char *sync_url, 440 const char *account_reference, 441 unsigned int http_status, 442 uint32_t expected_total_blocks, 443 const uint32_t *prefix, 444 unsigned int num_local, 445 const struct SYNC_BlockNonce *local_nonces, 446 const struct GNUNET_HashCode *local_hashes, 447 unsigned int num_missing, 448 const struct SYNC_BlockNonce *missing_nonces, 449 const struct GNUNET_HashCode *missing_hashes, 450 unsigned int num_stale, 451 const struct SYNC_BlockNonce *stale_nonces, 452 const struct GNUNET_HashCode *stale_hashes) 453 { 454 struct BlockReconcileState *brs; 455 456 GNUNET_assert (num_local <= MAX_RECONCILE_ENTRIES); 457 GNUNET_assert (num_missing <= MAX_RECONCILE_ENTRIES); 458 GNUNET_assert (num_stale <= MAX_RECONCILE_ENTRIES); 459 brs = GNUNET_new (struct BlockReconcileState); 460 brs->sync_url = sync_url; 461 brs->account_reference = account_reference; 462 brs->http_status = http_status; 463 brs->expected_total_blocks = expected_total_blocks; 464 if (NULL != prefix) 465 { 466 brs->prefix = GNUNET_new (uint32_t); 467 *brs->prefix = *prefix; 468 } 469 else 470 brs->prefix = NULL; 471 brs->num_local = num_local; 472 if (0 < num_local) 473 { 474 GNUNET_memcpy (brs->local_nonces, 475 local_nonces, 476 num_local * sizeof (struct SYNC_BlockNonce)); 477 GNUNET_memcpy (brs->local_hashes, 478 local_hashes, 479 num_local * sizeof (struct GNUNET_HashCode)); 480 } 481 brs->num_missing = num_missing; 482 if (0 < num_missing) 483 { 484 GNUNET_memcpy (brs->missing_nonces, 485 missing_nonces, 486 num_missing * sizeof (struct SYNC_BlockNonce)); 487 GNUNET_memcpy (brs->missing_hashes, 488 missing_hashes, 489 num_missing * sizeof (struct GNUNET_HashCode)); 490 } 491 brs->num_stale = num_stale; 492 if (0 < num_stale) 493 { 494 GNUNET_memcpy (brs->stale_nonces, 495 stale_nonces, 496 num_stale * sizeof (struct SYNC_BlockNonce)); 497 GNUNET_memcpy (brs->stale_hashes, 498 stale_hashes, 499 num_stale * sizeof (struct GNUNET_HashCode)); 500 } 501 { 502 struct TALER_TESTING_Command cmd = { 503 .cls = brs, 504 .label = label, 505 .run = &reconcile_run, 506 .cleanup = &reconcile_cleanup 507 }; 508 509 return cmd; 510 } 511 } 512 513 514 /* end of testing_api_cmd_block_reconcile.c */