test_sync_api.c (29763B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2014-2024 Taler Systems SA 4 5 TALER 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 TALER 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 TALER; see the file COPYING. If not, see 17 <http://www.gnu.org/licenses/> 18 */ 19 /** 20 * @file testing/test_sync_api.c 21 * @brief testcase to test sync's HTTP API interface 22 * @author Christian Grothoff 23 */ 24 #include "platform.h" 25 #include <taler/taler_util.h> 26 #include <taler/taler_signatures.h> 27 #include <taler/taler_exchange_service.h> 28 #include <taler/taler_json_lib.h> 29 #include <gnunet/gnunet_util_lib.h> 30 /* The taler testing library still includes the MHD1 header 31 (microhttpd.h); until that is resolved, include it here too, 32 as the two headers cannot be mixed in one translation unit. */ 33 #include <microhttpd.h> 34 #include <taler/taler_bank_service.h> 35 #include <taler/taler_testing_lib.h> 36 #include <taler/taler_merchant_testing_lib.h> 37 #include <taler/taler_error_codes.h> 38 #include "sync/sync_service.h" 39 #include "sync/sync_testing_lib.h" 40 #include "sync_util.h" 41 42 43 /** 44 * Configuration file we use. One (big) configuration is used 45 * for the various components for this test. 46 */ 47 #define CONFIG_FILE "test_sync_api.conf" 48 49 /** 50 * Account number of some user. 51 */ 52 #define USER_ACCOUNT_NAME "62" 53 54 /** 55 * Account number used by the merchant 56 */ 57 #define MERCHANT_ACCOUNT_NAME "3" 58 59 /** 60 * Payto URI of the customer (payer). 61 */ 62 static struct TALER_FullPayto payer_payto; 63 64 /** 65 * Payto URI of the merchant (receiver). 66 */ 67 static struct TALER_FullPayto merchant_payto; 68 69 /** 70 * Configuration of the bank. 71 */ 72 static struct TALER_TESTING_Credentials cred; 73 74 /** 75 * Merchant base URL. 76 */ 77 static const char *merchant_url = "http://localhost:8080/"; 78 79 /** 80 * Sync base URL. 81 */ 82 static const char *sync_url = "http://localhost:8084/"; 83 84 85 /** 86 * Execute the taler-exchange-wirewatch command with 87 * our configuration file. 88 * 89 * @param label label to use for the command. 90 */ 91 static struct TALER_TESTING_Command 92 cmd_exec_wirewatch (const char *label) 93 { 94 return TALER_TESTING_cmd_exec_wirewatch (label, 95 CONFIG_FILE); 96 } 97 98 99 /** 100 * Run wire transfer of funds from some user's account to the 101 * exchange. 102 * 103 * @param label label to use for the command. 104 * @param amount amount to transfer, i.e. "EUR:1" 105 * @param url exchange_url 106 */ 107 static struct TALER_TESTING_Command 108 cmd_transfer_to_exchange (const char *label, 109 const char *amount) 110 { 111 return TALER_TESTING_cmd_admin_add_incoming (label, 112 amount, 113 &cred.ba, 114 payer_payto); 115 } 116 117 118 /** 119 * Main function that will tell the interpreter what commands to 120 * run. 121 * 122 * @param cls closure 123 * @param is interpreter state 124 */ 125 static void 126 run (void *cls, 127 struct TALER_TESTING_Interpreter *is) 128 { 129 /* Payload of the blocks we upload. data[0] is the initial content of 130 block A, which is later updated to data[1]; hashes[] holds the 131 hashes of the *final* content of blocks A, C and D. */ 132 static const char *const data[] = { 133 "Hello World", 134 "Block B data", 135 "Block C data", 136 "Block D data" 137 }; 138 static const char obj_data_A[] = "object-A-data"; 139 static const char obj_data_B[] = "object-B-data"; 140 /* Nonces of blocks A, C and D, in chain order. */ 141 struct SYNC_BlockNonce nonces[3]; 142 /* Hashes of blocks A, C and D, in chain order. */ 143 struct GNUNET_HashCode hashes[3]; 144 /* Mixing salt used for the reconcile tests. */ 145 uint32_t reconcile_prefix; 146 /* Nonce of a block that is never successfully uploaded. */ 147 struct SYNC_BlockNonce nonce_stale; 148 /* Nonce that never exists on the server. */ 149 struct SYNC_BlockNonce bogus_nonce; 150 /* Nonces of the two blocks used for the refcount tests. */ 151 struct SYNC_BlockNonce ref_nonce; 152 struct SYNC_BlockNonce ref_nonce2; 153 /* Nonce of a block whose append is rejected for a bad object ref. */ 154 struct SYNC_BlockNonce orphan_nonce; 155 /* Nonce of the block appended after that rejected append. */ 156 struct SYNC_BlockNonce tail_nonce; 157 /* UIDs of the two objects used for the refcount tests. */ 158 struct SYNC_ObjectUID obj_uids[2]; 159 /* UID of an object that is never uploaded. */ 160 struct SYNC_ObjectUID unknown_uid; 161 const int16_t ref_incs[2] = { 1, 1 }; 162 const int16_t ref_decs[2] = { -1, -1 }; 163 const int16_t ref_inc1[1] = { 1 }; 164 const int16_t ref_dec2[1] = { -2 }; 165 166 (void) cls; 167 for (unsigned int i = 0; i < 3; i++) 168 SYNC_block_nonce_generate (&nonces[i]); 169 SYNC_block_nonce_generate (&nonce_stale); 170 SYNC_block_nonce_generate (&bogus_nonce); 171 SYNC_block_nonce_generate (&ref_nonce); 172 SYNC_block_nonce_generate (&ref_nonce2); 173 SYNC_block_nonce_generate (&orphan_nonce); 174 SYNC_block_nonce_generate (&tail_nonce); 175 for (unsigned int i = 0; i < 2; i++) 176 GNUNET_CRYPTO_random_block (&obj_uids[i], 177 sizeof (obj_uids[i])); 178 GNUNET_CRYPTO_random_block (&unknown_uid, 179 sizeof (unknown_uid)); 180 for (unsigned int i = 0; i < 3; i++) 181 GNUNET_CRYPTO_hash (data[i + 1], 182 strlen (data[i + 1]), 183 &hashes[i]); 184 reconcile_prefix = 0x01020304; 185 186 { 187 struct TALER_TESTING_Command commands[] = { 188 TALER_TESTING_cmd_system_start ("start-taler", 189 CONFIG_FILE, 190 "-efms", 191 "-u", "exchange-account-exchange", 192 "-r", "merchant-exchange-default", 193 NULL), 194 TALER_TESTING_cmd_get_exchange ("get-exchange", 195 cred.cfg, 196 NULL, 197 true, 198 true), 199 TALER_TESTING_cmd_merchant_post_instances ("instance-create-admin", 200 merchant_url, 201 "admin", 202 MHD_HTTP_NO_CONTENT), 203 TALER_TESTING_cmd_merchant_post_account ( 204 "instance-create-default-account", 205 merchant_url, 206 merchant_payto, 207 NULL, NULL, 208 MHD_HTTP_OK), 209 210 /** 211 * Move money to the exchange's bank account. 212 */ 213 cmd_transfer_to_exchange ("create-reserve-1", 214 "EUR:10.02"), 215 /** 216 * Make a reserve exist, according to the previous 217 * transfer. 218 */ 219 cmd_exec_wirewatch ("wirewatch-1"), 220 TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1", 221 "create-reserve-1", 222 "EUR:5", 223 0, 224 MHD_HTTP_OK), 225 TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2", 226 "create-reserve-1", 227 "EUR:5", 228 0, 229 MHD_HTTP_OK), 230 231 /* Upload first block; no lifetime yet → 402 Payment Required */ 232 SYNC_TESTING_cmd_block_append ("upload-block-1", 233 sync_url, 234 NULL, 235 &nonces[0], 236 MHD_HTTP_PAYMENT_REQUIRED, 237 data[0], 238 strlen (data[0]), 239 true, 240 0, 241 NULL, 242 NULL), 243 /* Claim the order from the merchant */ 244 TALER_TESTING_cmd_merchant_claim_order ("fetch-proposal", 245 merchant_url, 246 MHD_HTTP_OK, 247 "upload-block-1", 248 NULL), 249 /* Pay with one withdrawn coin */ 250 TALER_TESTING_cmd_merchant_pay_order ("pay-account", 251 merchant_url, 252 MHD_HTTP_OK, 253 "fetch-proposal", 254 "withdraw-coin-1", 255 "EUR:5", 256 "EUR:4.99", 257 "session-id"), 258 /* Retry same upload; server finds paid order → 204 */ 259 SYNC_TESTING_cmd_block_append ("upload-block-2", 260 sync_url, 261 "upload-block-1", 262 &nonces[0], 263 MHD_HTTP_NO_CONTENT, 264 data[0], 265 strlen (data[0]), 266 true, 267 0, 268 NULL, 269 NULL), 270 /* Update the existing block with different content */ 271 SYNC_TESTING_cmd_block_update ("upload-block-B", 272 sync_url, 273 "upload-block-2", 274 &nonces[0], 275 NULL, 276 MHD_HTTP_NO_CONTENT, 277 data[1], 278 strlen (data[1]), 279 0, 280 NULL, 281 NULL), 282 /* Append a new block C at the end of block B */ 283 SYNC_TESTING_cmd_block_append ("upload-block-C", 284 sync_url, 285 "upload-block-B", 286 &nonces[1], 287 MHD_HTTP_NO_CONTENT, 288 data[2], 289 strlen (data[2]), 290 false, 291 0, 292 NULL, 293 NULL), 294 /* List blocks for the account */ 295 SYNC_TESTING_cmd_block_list ("list-blocks", 296 sync_url, 297 "upload-block-2", 298 10, 299 NULL, 300 MHD_HTTP_OK, 301 2, 302 nonces, 303 hashes), 304 305 /* Append with stale prev_nonce (nonces[0]), 306 but last_block is now nonces[1] → 409 */ 307 SYNC_TESTING_cmd_block_append ("append-stale-prev", 308 sync_url, 309 "upload-block-B", 310 &nonce_stale, 311 MHD_HTTP_CONFLICT, 312 data[0], 313 strlen (data[0]), 314 false, 315 0, 316 NULL, 317 NULL), 318 /* Update with stale old_data_hash 319 (references upload-block-2 whose curr_hash is hash("Hello World"), 320 but block now has hash("Block B data")) → 409 */ 321 SYNC_TESTING_cmd_block_update ("update-conflict", 322 sync_url, 323 "upload-block-2", 324 &nonces[0], 325 NULL, 326 MHD_HTTP_CONFLICT, 327 data[2], 328 strlen (data[2]), 329 0, 330 NULL, 331 NULL), 332 /* Re-upload same data → 304 Not Modified */ 333 SYNC_TESTING_cmd_block_append ("reupload-same-data", 334 sync_url, 335 "upload-block-B", 336 &nonces[0], 337 MHD_HTTP_NOT_MODIFIED, 338 data[1], 339 strlen (data[1]), 340 true, 341 0, 342 NULL, 343 NULL), 344 /* List with non-existent start_nonce → 404 */ 345 SYNC_TESTING_cmd_block_list ("list-bogus-start", 346 sync_url, 347 "upload-block-2", 348 10, 349 &bogus_nonce, 350 MHD_HTTP_NOT_FOUND, 351 0, 352 NULL, 353 NULL), 354 /* Final list confirms error cases left state intact */ 355 SYNC_TESTING_cmd_block_list ("list-blocks-final", 356 sync_url, 357 "upload-block-2", 358 10, 359 NULL, 360 MHD_HTTP_OK, 361 2, 362 nonces, 363 hashes), 364 365 /* ========== Delete block tests ========== */ 366 367 /* Upload a third block D */ 368 SYNC_TESTING_cmd_block_append ("upload-block-D", 369 sync_url, 370 "upload-block-C", 371 &nonces[2], 372 MHD_HTTP_NO_CONTENT, 373 data[3], 374 strlen (data[3]), 375 false, 376 0, 377 NULL, 378 NULL), 379 /* List all 3 blocks */ 380 SYNC_TESTING_cmd_block_list ("list-3-blocks", 381 sync_url, 382 "upload-block-D", 383 10, 384 NULL, 385 MHD_HTTP_OK, 386 3, 387 nonces, 388 hashes), 389 /* Delete block D */ 390 SYNC_TESTING_cmd_block_delete ("delete-block-D", 391 sync_url, 392 "upload-block-D", 393 MHD_HTTP_NO_CONTENT, 394 0, 395 NULL, 396 NULL), 397 /* List: only A and B remain */ 398 SYNC_TESTING_cmd_block_list ("list-after-delete-D", 399 sync_url, 400 "upload-block-2", 401 10, 402 NULL, 403 MHD_HTTP_OK, 404 2, 405 nonces, 406 hashes), 407 /* Delete block D again → 404 (already gone) */ 408 SYNC_TESTING_cmd_block_delete ("delete-block-D-again", 409 sync_url, 410 "upload-block-D", 411 MHD_HTTP_NOT_FOUND, 412 0, 413 NULL, 414 NULL), 415 416 /* ========== Reconcile (invertible bloom filter) tests ========== */ 417 418 /* A wallet holding exactly the server's blocks decodes an empty 419 difference from the filter. */ 420 SYNC_TESTING_cmd_block_reconcile ("reconcile-in-sync", 421 sync_url, 422 "upload-block-2", 423 MHD_HTTP_OK, 424 2, 425 NULL, 426 2, 427 nonces, 428 hashes, 429 0, 430 NULL, 431 NULL, 432 0, 433 NULL, 434 NULL), 435 /* With a fresh salt the filter still matches every block, and 436 the reply echoes the requested salt. */ 437 SYNC_TESTING_cmd_block_reconcile ("reconcile-prefix", 438 sync_url, 439 "upload-block-2", 440 MHD_HTTP_OK, 441 2, 442 &reconcile_prefix, 443 2, 444 nonces, 445 hashes, 446 0, 447 NULL, 448 NULL, 449 0, 450 NULL, 451 NULL), 452 /* A wallet that still holds the deleted block D sees it as 453 stale (local-only); nothing is missing. */ 454 SYNC_TESTING_cmd_block_reconcile ("reconcile-stale", 455 sync_url, 456 "upload-block-2", 457 MHD_HTTP_OK, 458 2, 459 NULL, 460 3, 461 nonces, 462 hashes, 463 0, 464 NULL, 465 NULL, 466 1, 467 &nonces[2], 468 &hashes[2]), 469 /* A wallet holding only block A enumerates block B as missing 470 (server-only). */ 471 SYNC_TESTING_cmd_block_reconcile ("reconcile-missing", 472 sync_url, 473 "upload-block-2", 474 MHD_HTTP_OK, 475 2, 476 NULL, 477 1, 478 nonces, 479 hashes, 480 1, 481 &nonces[1], 482 &hashes[1], 483 0, 484 NULL, 485 NULL), 486 487 /* ========== Single block fetch tests ========== */ 488 489 /* A block is fetchable by nonce through the list endpoint with 490 limit 1 and an inclusive start_nonce, with its payload and 491 signature intact. */ 492 SYNC_TESTING_cmd_block_list ("fetch-block-B", 493 sync_url, 494 "upload-block-2", 495 1, 496 &nonces[1], 497 MHD_HTTP_OK, 498 1, 499 &nonces[1], 500 &hashes[1]), 501 /* A nonce that is not part of the linked list is not found. */ 502 SYNC_TESTING_cmd_block_list ("fetch-block-unknown", 503 sync_url, 504 "upload-block-2", 505 1, 506 &bogus_nonce, 507 MHD_HTTP_NOT_FOUND, 508 0, 509 NULL, 510 NULL), 511 /* Final list confirms all is well */ 512 SYNC_TESTING_cmd_block_list ("list-blocks-end", 513 sync_url, 514 "upload-block-2", 515 10, 516 NULL, 517 MHD_HTTP_OK, 518 2, 519 nonces, 520 hashes), 521 522 /* ========== Object store and refcount tests ========== */ 523 524 /* Upload object A */ 525 SYNC_TESTING_cmd_object_upload ("upload-obj-A", 526 sync_url, 527 "upload-block-2", 528 &obj_uids[0], 529 MHD_HTTP_NO_CONTENT, 530 obj_data_A, 531 sizeof obj_data_A - 1), 532 /* Upload object B */ 533 SYNC_TESTING_cmd_object_upload ("upload-obj-B", 534 sync_url, 535 "upload-block-2", 536 &obj_uids[1], 537 MHD_HTTP_NO_CONTENT, 538 obj_data_B, 539 sizeof obj_data_B - 1), 540 541 /* Fetch object A and verify content and hash */ 542 SYNC_TESTING_cmd_object_fetch ("fetch-obj-A", 543 sync_url, 544 "upload-block-2", 545 &obj_uids[0], 546 MHD_HTTP_OK, 547 obj_data_A, 548 sizeof obj_data_A - 1), 549 550 /* Fetch object B and verify content and hash */ 551 SYNC_TESTING_cmd_object_fetch ("fetch-obj-B", 552 sync_url, 553 "upload-block-2", 554 &obj_uids[1], 555 MHD_HTTP_OK, 556 obj_data_B, 557 sizeof obj_data_B - 1), 558 559 /* Try uploading a duplicate UID → idempotent (204) */ 560 SYNC_TESTING_cmd_object_upload ("upload-obj-A-dup", 561 sync_url, 562 "upload-block-2", 563 &obj_uids[0], 564 MHD_HTTP_NO_CONTENT, 565 obj_data_B, 566 sizeof obj_data_B - 1), 567 568 /* Upload a block that references objects A (+1) and B (+1) */ 569 SYNC_TESTING_cmd_block_append ("upload-block-refs", 570 sync_url, 571 "upload-block-C", 572 &ref_nonce, 573 MHD_HTTP_NO_CONTENT, 574 data[0], 575 strlen (data[0]), 576 false, 577 2, 578 obj_uids, 579 ref_incs), 580 581 /* Delete the referencing block with matching refs (decrement). 582 Objects reach refcount 0 and are auto-deleted from the DB. */ 583 SYNC_TESTING_cmd_block_delete ("delete-block-refs", 584 sync_url, 585 "upload-block-refs", 586 MHD_HTTP_NO_CONTENT, 587 2, 588 obj_uids, 589 ref_decs), 590 591 /* Re-upload object A (it was deleted when refcount hit 0) */ 592 SYNC_TESTING_cmd_object_upload ("upload-obj-A-again", 593 sync_url, 594 "upload-block-2", 595 &obj_uids[0], 596 MHD_HTTP_NO_CONTENT, 597 obj_data_A, 598 sizeof obj_data_A - 1), 599 600 /* Upload another block referencing A (+1) */ 601 SYNC_TESTING_cmd_block_append ("upload-block-refs2", 602 sync_url, 603 "upload-block-C", 604 &ref_nonce2, 605 MHD_HTTP_NO_CONTENT, 606 data[0], 607 strlen (data[0]), 608 false, 609 1, 610 obj_uids, 611 ref_inc1), 612 613 /* Try to delete with refs that would underflow → 409 conflict */ 614 SYNC_TESTING_cmd_block_delete ("delete-block-refs-underflow", 615 sync_url, 616 "upload-block-refs2", 617 MHD_HTTP_CONFLICT, 618 1, 619 obj_uids, 620 ref_dec2), 621 /* The rejected delete left the block listed ... */ 622 SYNC_TESTING_cmd_block_list ("list-after-underflow", 623 sync_url, 624 "upload-block-2", 625 10, 626 NULL, 627 MHD_HTTP_OK, 628 0, 629 NULL, 630 NULL), 631 /* ... and the object it references is still fetchable */ 632 SYNC_TESTING_cmd_object_fetch ("fetch-obj-A-after-underflow", 633 sync_url, 634 "upload-block-2", 635 &obj_uids[0], 636 MHD_HTTP_OK, 637 obj_data_A, 638 sizeof obj_data_A - 1), 639 640 /* ========== Object and reference error paths ========== */ 641 642 /* Fetching an object that was never uploaded → 404 */ 643 SYNC_TESTING_cmd_object_fetch ("fetch-obj-unknown", 644 sync_url, 645 "upload-block-2", 646 &unknown_uid, 647 MHD_HTTP_NOT_FOUND, 648 NULL, 649 0), 650 651 /* Referencing an object that was never uploaded → 409 ... */ 652 SYNC_TESTING_cmd_block_append ("append-unknown-object-ref", 653 sync_url, 654 "upload-block-refs2", 655 &orphan_nonce, 656 MHD_HTTP_CONFLICT, 657 data[0], 658 strlen (data[0]), 659 false, 660 1, 661 &unknown_uid, 662 ref_inc1), 663 /* ... and must not have moved the tail, so the next append 664 onto upload-block-refs2 still succeeds */ 665 SYNC_TESTING_cmd_block_append ("append-after-rejected-ref", 666 sync_url, 667 "upload-block-refs2", 668 &tail_nonce, 669 MHD_HTTP_NO_CONTENT, 670 data[2], 671 strlen (data[2]), 672 false, 673 0, 674 NULL, 675 NULL), 676 677 TALER_TESTING_cmd_end () 678 }; 679 680 TALER_TESTING_run (is, 681 commands); 682 } 683 } 684 685 686 int 687 main (int argc, 688 char *const *argv) 689 { 690 (void) argc; 691 payer_payto.full_payto = 692 (char *) "payto://x-taler-bank/localhost/" USER_ACCOUNT_NAME 693 "?receiver-name=user"; 694 merchant_payto.full_payto = 695 (char *) "payto://x-taler-bank/localhost/" MERCHANT_ACCOUNT_NAME 696 "?receiver-name=merchant"; 697 return TALER_TESTING_main (argv, 698 "DEBUG", 699 CONFIG_FILE, 700 "exchange-account-exchange", 701 TALER_TESTING_BS_FAKEBANK, 702 &cred, 703 &run, 704 NULL); 705 } 706 707 708 /* end of test_sync_api.c */