|
5 | 5 | #include "util/asio.h" |
6 | 6 | #include "crypto/Hex.h" |
7 | 7 | #include "crypto/KeyUtils.h" |
| 8 | +#include "crypto/SecretKey.h" |
8 | 9 | #include "database/Database.h" |
9 | 10 | #include "ledger/LedgerHeaderUtils.h" |
10 | 11 | #include "ledger/LedgerTxn.h" |
11 | 12 | #include "ledger/test/LedgerTestUtils.h" |
12 | 13 | #include "lib/util/stdrandom.h" |
13 | 14 | #include "main/Application.h" |
| 15 | +#include "main/BannedAccountsPersistor.h" |
14 | 16 | #include "main/Config.h" |
15 | 17 | #include "main/PersistentState.h" |
16 | 18 | #include "overlay/BanManager.h" |
|
26 | 28 | #include <algorithm> |
27 | 29 | #include <optional> |
28 | 30 | #include <random> |
| 31 | +#include <set> |
29 | 32 |
|
30 | 33 | using namespace stellar; |
31 | 34 |
|
@@ -671,3 +674,127 @@ TEST_CASE("ledgerheaders migration works correctly", "[db]") |
671 | 674 | checkMigration(headerEncoded); |
672 | 675 | } |
673 | 676 | } |
| 677 | + |
| 678 | +#ifdef USE_POSTGRES |
| 679 | +TEST_CASE("schema parity across DB backends", "[db][schematest]") |
| 680 | +{ |
| 681 | + // This test verifies that after initialization, persistent SQLite (with |
| 682 | + // main + misc DB split) and PostgreSQL end up with the exact same set of |
| 683 | + // tables and the same row counts. It catches bugs where a table or schema |
| 684 | + // upgrade is applied for one backend but not the other. |
| 685 | + |
| 686 | + // Helper: get sorted table names from a SQLite session. |
| 687 | + auto getSqliteTables = [](Database& db, SessionWrapper& session) { |
| 688 | + std::set<std::string> tables; |
| 689 | + std::string name; |
| 690 | + auto prep = db.getPreparedStatement( |
| 691 | + "SELECT name FROM sqlite_master WHERE type='table' " |
| 692 | + "AND name NOT LIKE 'sqlite_%' ORDER BY name", |
| 693 | + session); |
| 694 | + auto& st = prep.statement(); |
| 695 | + st.exchange(soci::into(name)); |
| 696 | + st.define_and_bind(); |
| 697 | + st.execute(false); |
| 698 | + while (st.fetch()) |
| 699 | + { |
| 700 | + tables.insert(name); |
| 701 | + } |
| 702 | + return tables; |
| 703 | + }; |
| 704 | + |
| 705 | + // Helper: count rows in a table. |
| 706 | + auto countRows = [](soci::session& sess, std::string const& table) { |
| 707 | + int count = 0; |
| 708 | + soci::statement st = (sess.prepare << "SELECT COUNT(*) FROM " + table, |
| 709 | + soci::into(count)); |
| 710 | + st.execute(true); |
| 711 | + return count; |
| 712 | + }; |
| 713 | + |
| 714 | + // ---- Build the SQLite persistent reference (main + misc split) ---- |
| 715 | + TmpDir tmpDir("schema-parity-test"); |
| 716 | + Config cfg1 = getTestConfig(0, Config::TESTDB_BUCKET_DB_PERSISTENT); |
| 717 | + cfg1.DATABASE = SecretValue{"sqlite3://" + tmpDir.getName() + "/test.db"}; |
| 718 | + // Use non-empty FILTERED_G_ADDRESSES to test migration as well |
| 719 | + cfg1.FILTERED_G_ADDRESSES = { |
| 720 | + "GBO7VUL2TOKPWFAWKATIW7K3QYA7WQ63VDY5CAE6AFUUX6BHZBOC2WXC", |
| 721 | + "GATDQL767ZM2JQTBEG4BQ5WKOQNGAGWZDUN4GYT2UINPEU3RT2UAMVZH"}; |
| 722 | + |
| 723 | + VirtualClock clock1; |
| 724 | + Application::pointer app1 = createTestApplication(clock1, cfg1); |
| 725 | + auto& db1 = app1->getDatabase(); |
| 726 | + |
| 727 | + REQUIRE(db1.canUseMiscDB()); |
| 728 | + REQUIRE(db1.getMainDBSchemaVersion() == SCHEMA_VERSION); |
| 729 | + REQUIRE(db1.getMiscDBSchemaVersion() == MISC_SCHEMA_VERSION); |
| 730 | + |
| 731 | + // Union of main + misc tables is the full set |
| 732 | + auto mainTables = getSqliteTables(db1, db1.getSession()); |
| 733 | + auto miscTables = getSqliteTables(db1, db1.getMiscSession()); |
| 734 | + |
| 735 | + // Main and misc must not overlap |
| 736 | + for (auto const& t : mainTables) |
| 737 | + { |
| 738 | + INFO("Table in both main and misc: " << t); |
| 739 | + REQUIRE(miscTables.count(t) == 0); |
| 740 | + } |
| 741 | + |
| 742 | + std::set<std::string> allSqliteTables = mainTables; |
| 743 | + allSqliteTables.insert(miscTables.begin(), miscTables.end()); |
| 744 | + |
| 745 | + // ---- PostgreSQL: compare tables and row counts ---- |
| 746 | + Config cfg2 = getTestConfig(1, Config::TESTDB_POSTGRESQL); |
| 747 | + cfg2.FILTERED_G_ADDRESSES = { |
| 748 | + "GBO7VUL2TOKPWFAWKATIW7K3QYA7WQ63VDY5CAE6AFUUX6BHZBOC2WXC", |
| 749 | + "GATDQL767ZM2JQTBEG4BQ5WKOQNGAGWZDUN4GYT2UINPEU3RT2UAMVZH"}; |
| 750 | + |
| 751 | + VirtualClock clock2; |
| 752 | + Application::pointer app2 = createTestApplication(clock2, cfg2); |
| 753 | + auto& db2 = app2->getDatabase(); |
| 754 | + |
| 755 | + REQUIRE_FALSE(db2.canUseMiscDB()); |
| 756 | + REQUIRE(db2.getMainDBSchemaVersion() == SCHEMA_VERSION); |
| 757 | + |
| 758 | + // Get Postgres table names |
| 759 | + std::set<std::string> pgTables; |
| 760 | + { |
| 761 | + std::string name; |
| 762 | + soci::statement st = |
| 763 | + (db2.getRawSession().prepare << "SELECT tablename FROM pg_tables " |
| 764 | + "WHERE schemaname = 'public' " |
| 765 | + "ORDER BY tablename", |
| 766 | + soci::into(name)); |
| 767 | + st.execute(false); |
| 768 | + while (st.fetch()) |
| 769 | + { |
| 770 | + pgTables.insert(name); |
| 771 | + } |
| 772 | + } |
| 773 | + |
| 774 | + // Must have the exact same tables |
| 775 | + CHECK(pgTables == allSqliteTables); |
| 776 | + |
| 777 | + // Verify every table has the same row count across both backends. |
| 778 | + // slotstate has an extra row in SQLite misc DB for the misc schema |
| 779 | + // version, which doesn't exist in Postgres (it uses storestate). |
| 780 | + for (auto const& table : allSqliteTables) |
| 781 | + { |
| 782 | + // For SQLite, query the right session (main or misc) |
| 783 | + auto& sqliteSess = miscTables.count(table) ? db1.getRawMiscSession() |
| 784 | + : db1.getRawSession(); |
| 785 | + int sqliteRows = countRows(sqliteSess, table); |
| 786 | + int pgRows = countRows(db2.getRawSession(), table); |
| 787 | + |
| 788 | + INFO("Table: " << table); |
| 789 | + if (table == "slotstate") |
| 790 | + { |
| 791 | + // SQLite misc DB has one extra row for miscdatabaseschema |
| 792 | + CHECK(sqliteRows == pgRows + 1); |
| 793 | + } |
| 794 | + else |
| 795 | + { |
| 796 | + CHECK(sqliteRows == pgRows); |
| 797 | + } |
| 798 | + } |
| 799 | +} |
| 800 | +#endif |
0 commit comments