Skip to content

Commit 1befa1c

Browse files
Merge v25.2.0 and v25.2.1 into master (#5190)
2 parents fbe7a46 + c3e739a commit 1befa1c

71 files changed

Lines changed: 2842 additions & 212 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitmodules

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
[submodule "src/rust/soroban/p25"]
4747
path = src/rust/soroban/p25
4848
url = https://github.com/stellar/rs-soroban-env.git
49+
branch = internal/v25.2.0
4950
[submodule "src/rust/soroban/p26"]
5051
path = src/rust/soroban/p26
5152
url = https://github.com/stellar/rs-soroban-env.git

docs/software/commands.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,28 @@ Most commands return their results in JSON format.
252252
* **bans**
253253
List current active bans
254254

255+
* **banaccounts**
256+
Manages the persistent list of banned accounts. Banned accounts are stored in
257+
the database and survive restarts. Any transaction whose source account,
258+
operation source account, fee-bump fee source, or (for Soroban transactions)
259+
write footprint account entry matches a banned address will be rejected from
260+
the transaction queue.
261+
* `banaccounts`<br>
262+
Lists the currently banned account addresses as a JSON array.<br>
263+
* `banaccounts?accountids=G_ADDRESS1,G_ADDRESS2,...`<br>
264+
Adds the specified addresses to the persistent ban list. Existing bans are
265+
preserved (additive).<br>
266+
267+
Note: The `FILTERED_G_ADDRESSES` configuration option is deprecated. Any
268+
addresses configured there will be automatically migrated to the persistent
269+
ban list on startup.
270+
271+
* **unbanaccounts**
272+
* `unbanaccounts`<br>
273+
Clears all banned accounts.<br>
274+
* `unbanaccounts?accountids=G_ADDRESS1,G_ADDRESS2,...`<br>
275+
Removes the specified addresses from the persistent ban list.<br>
276+
255277
* **checkdb**
256278
Triggers the instance to perform a background check of the database's state.
257279

@@ -337,6 +359,12 @@ Most commands return their results in JSON format.
337359
The network is under high load and the fee is too low.
338360
* "FILTERED" - transaction rejected because it contains an operation type that Stellar Core filters out. See Stellar Core configuration `EXCLUDE_TRANSACTIONS_CONTAINING_OPERATION_TYPE` for more details.
339361

362+
Optional parameters:
363+
* `force=true` - bypasses banned account filtering (see `banaccounts`),
364+
allowing the transaction into the mempool even if its source account or
365+
fee source is on the ban list. Other filtering (operation type, Soroban
366+
key filtering) still applies. Example: `tx?blob=Base64&force=true`
367+
340368
* **upgrades**
341369
* `upgrades?mode=get`<br>
342370
Retrieves the currently configured upgrade settings.<br>

src/catchup/ApplyCheckpointWork.cpp

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -360,10 +360,19 @@ ApplyCheckpointWork::onRun()
360360
openInputFiles();
361361
}
362362

363-
auto lcd = getNextLedgerCloseData();
364-
if (!lcd)
363+
std::shared_ptr<LedgerCloseData> lcd;
364+
try
365365
{
366-
return State::WORK_RUNNING;
366+
lcd = getNextLedgerCloseData();
367+
if (!lcd)
368+
{
369+
return State::WORK_RUNNING;
370+
}
371+
}
372+
catch (std::runtime_error const& e)
373+
{
374+
CLOG_ERROR(History, "ApplyCheckpointWork failed: {}", e.what());
375+
return State::WORK_FAILURE;
367376
}
368377

369378
auto applyLedger = std::make_shared<ApplyLedgerWork>(mApp, *lcd);

src/catchup/DownloadApplyTxsWork.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ DownloadApplyTxsWork::DownloadApplyTxsWork(
2424
Application& app, TmpDir const& downloadDir, LedgerRange const& range,
2525
LedgerHeaderHistoryEntry& lastApplied, bool waitForPublish,
2626
std::shared_ptr<HistoryArchive> archive)
27-
: BatchWork(app, "download-apply-ledgers")
27+
: BatchWork(app, "download-apply-ledgers", BasicWork::RETRY_A_FEW)
2828
, mRange(range)
2929
, mDownloadDir(downloadDir)
3030
, mLastApplied(lastApplied)
@@ -223,7 +223,7 @@ void
223223
DownloadApplyTxsWork::resetIter()
224224
{
225225
mCheckpointToQueue = HistoryManager::checkpointContainingLedger(
226-
mRange.mFirst, mApp.getConfig());
226+
mApp.getLedgerManager().getLastClosedLedgerNum() + 1, mApp.getConfig());
227227
mLastYieldedWork.reset();
228228
mLastApplied = mApp.getLedgerManager().getLastClosedLedgerHeader();
229229
}

src/catchup/VerifyLedgerChainWork.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,13 @@ VerifyLedgerChainWork::verifyHistoryOfSingleCheckpoint()
200200
{
201201
return HistoryManager::VERIFY_STATUS_ERR_BAD_LEDGER_VERSION;
202202
}
203+
catch (std::runtime_error const& e)
204+
{
205+
CLOG_ERROR(History,
206+
"Failed when verifying ledger chain with error {}",
207+
e.what());
208+
return HistoryManager::VERIFY_STATUS_ERR_CORRUPT_HEADER;
209+
}
203210

204211
if (curr.header.ledgerVersion >
205212
mApp.getConfig().LEDGER_PROTOCOL_VERSION)
@@ -546,6 +553,12 @@ VerifyLedgerChainWork::onRun()
546553
CLOG_ERROR(History, "{}", POSSIBLY_CORRUPTED_HISTORY);
547554
mApp.getLedgerApplyManager().ledgerChainsVerificationFailed();
548555
return BasicWork::State::WORK_FAILURE;
556+
case HistoryManager::VERIFY_STATUS_ERR_CORRUPT_HEADER:
557+
CLOG_ERROR(History, "Catchup material failed verification - "
558+
"corrupted header, propagating failure");
559+
CLOG_ERROR(History, "{}", POSSIBLY_CORRUPTED_HISTORY);
560+
mApp.getLedgerApplyManager().ledgerChainsVerificationFailed();
561+
return BasicWork::State::WORK_FAILURE;
549562
default:
550563
releaseAssert(false);
551564
throw std::runtime_error("unexpected VerifyLedgerChainWork state");

src/database/Database.cpp

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "database/DatabaseConnectionString.h"
88
#include "database/DatabaseTypeSpecificOperation.h"
99
#include "main/Application.h"
10+
#include "main/BannedAccountsPersistor.h"
1011
#include "main/Config.h"
1112
#include "overlay/StellarXDR.h"
1213
#include "util/Decoder.h"
@@ -301,15 +302,20 @@ Database::applyMiscSchemaUpgrade(unsigned long vers)
301302
BanManager::maybeDropAndCreateNew(mMiscSession);
302303
// Copy contents from the main DB.
303304
populateMiscDatabase();
305+
tx.commit();
306+
// Detach the source database (attached by populateMiscDatabase)
307+
// _after_ commit to avoid "database is locked errors". If schema
308+
// version is already the most recent, DETACH is a no-op.
309+
getRawMiscSession() << "DETACH DATABASE source_db";
310+
return;
311+
case 2:
312+
// Add banned accounts table for persistent account filtering.
313+
BannedAccountsPersistor::maybeDropAndCreateNew(mMiscSession.session());
304314
break;
305315
default:
306316
throw std::runtime_error("Unknown DB schema version");
307317
}
308318
tx.commit();
309-
310-
// Detach the source database _after_ commit to avoid "database is locked
311-
// errors". If schema version is already the most recent, DETACH is a no-op.
312-
getRawMiscSession() << "DETACH DATABASE source_db";
313319
}
314320

315321
void
@@ -396,8 +402,18 @@ Database::applySchemaUpgrade(unsigned long vers)
396402
}
397403
break;
398404
case 27:
405+
// Add banned accounts table for persistent account filtering.
406+
// For SQLite-on-disk this is handled by misc schema upgrade v2;
407+
// for Postgres and in-memory SQLite, create in the main DB.
408+
if (!canUseMiscDB())
409+
{
410+
BannedAccountsPersistor::maybeDropAndCreateNew(getRawSession());
411+
}
412+
break;
413+
case 28:
399414
migrateLedgerHeadersToStoreState(*this);
400415
break;
416+
401417
default:
402418
throw std::runtime_error("Unknown DB schema version");
403419
}

src/database/Database.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ class Application;
2929

3030
// smallest schema version supported
3131
static constexpr unsigned long MIN_SCHEMA_VERSION = 25;
32-
static constexpr unsigned long SCHEMA_VERSION = 27;
32+
static constexpr unsigned long SCHEMA_VERSION = 28;
3333
static constexpr unsigned long FIRST_MAIN_VERSION_WITH_MISC = 26;
3434
// Misc schema version 0 means no misc table exists yet
3535
static constexpr unsigned long MIN_MISC_SCHEMA_VERSION = 0;
36-
static constexpr unsigned long MISC_SCHEMA_VERSION = 1;
36+
static constexpr unsigned long MISC_SCHEMA_VERSION = 2;
3737

3838
/**
3939
* Helper class for borrowing a SOCI prepared statement handle into a local

src/database/test/DatabaseTests.cpp

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
#include "util/asio.h"
66
#include "crypto/Hex.h"
77
#include "crypto/KeyUtils.h"
8+
#include "crypto/SecretKey.h"
89
#include "database/Database.h"
910
#include "ledger/LedgerHeaderUtils.h"
1011
#include "ledger/LedgerTxn.h"
1112
#include "ledger/test/LedgerTestUtils.h"
1213
#include "lib/util/stdrandom.h"
1314
#include "main/Application.h"
15+
#include "main/BannedAccountsPersistor.h"
1416
#include "main/Config.h"
1517
#include "main/PersistentState.h"
1618
#include "overlay/BanManager.h"
@@ -26,6 +28,7 @@
2628
#include <algorithm>
2729
#include <optional>
2830
#include <random>
31+
#include <set>
2932

3033
using namespace stellar;
3134

@@ -671,3 +674,127 @@ TEST_CASE("ledgerheaders migration works correctly", "[db]")
671674
checkMigration(headerEncoded);
672675
}
673676
}
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

src/herder/Herder.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,10 +139,11 @@ class Herder
139139
// generator, and therefore can skip certain expensive validity checks
140140
virtual TransactionQueue::AddResult
141141
recvTransaction(TransactionFrameBasePtr tx, bool submittedFromSelf,
142-
bool isLoadgenTx = false) = 0;
142+
bool force = false, bool isLoadgenTx = false) = 0;
143143
#else
144144
virtual TransactionQueue::AddResult
145-
recvTransaction(TransactionFrameBasePtr tx, bool submittedFromSelf) = 0;
145+
recvTransaction(TransactionFrameBasePtr tx, bool submittedFromSelf,
146+
bool force = false) = 0;
146147
#endif
147148
virtual void peerDoesntHave(stellar::MessageType type,
148149
uint256 const& itemID, Peer::pointer peer) = 0;
@@ -213,6 +214,9 @@ class Herder
213214
// gets the upgrades that are scheduled by this node
214215
virtual std::string getUpgradesJson() = 0;
215216

217+
// Override the filtered accounts at runtime using pre-parsed AccountIDs.
218+
virtual void setFilteredAccounts(std::set<AccountID> const& accounts) = 0;
219+
216220
virtual void forceSCPStateIntoSyncWithLastClosedLedger() = 0;
217221

218222
// helper function to craft an SCPValue

0 commit comments

Comments
 (0)