@@ -6030,6 +6030,163 @@ TEST_CASE("filter transactions by G address", "[herder]")
60306030 }
60316031}
60326032
6033+ TEST_CASE (" banaccounts HTTP command" , " [herder]" )
6034+ {
6035+ SECTION (" ban accounts via command" )
6036+ {
6037+ VirtualClock clock;
6038+ auto cfg = getTestConfig ();
6039+ cfg.FILTERED_G_ADDRESSES = {};
6040+ Application::pointer app = createTestApplication (clock, cfg);
6041+
6042+ auto root = app->getRoot ();
6043+ auto srcKey = SecretKey::pseudoRandomForTesting ();
6044+ auto src = root->create (srcKey, 1000000000 );
6045+
6046+ // Initially, no filter — transaction should be accepted
6047+ auto acc = getAccount (" acc" );
6048+ auto tx = src.tx ({createAccount (acc.getPublicKey (), 1 )});
6049+ REQUIRE (app->getHerder ().recvTransaction (tx, false ).code ==
6050+ TransactionQueue::AddResultCode::ADD_STATUS_PENDING );
6051+
6052+ // Now set the filter via the HTTP command
6053+ auto addr = KeyUtils::toStrKey (srcKey.getPublicKey ());
6054+ auto result = app->getCommandHandler ().manualCmd (
6055+ " banaccounts?accountids=" + addr);
6056+ REQUIRE (result.find (" filtered accounts updated" ) != std::string::npos);
6057+ REQUIRE (result.find (" \" count\" : 1" ) != std::string::npos);
6058+
6059+ // New transaction from the same source should now be filtered
6060+ auto acc2 = getAccount (" acc2" );
6061+ auto tx2 = src.tx ({createAccount (acc2.getPublicKey (), 1 )});
6062+ REQUIRE (app->getHerder ().recvTransaction (tx2, false ).code ==
6063+ TransactionQueue::AddResultCode::ADD_STATUS_FILTERED );
6064+ }
6065+
6066+ SECTION (" clear banned accounts via command" )
6067+ {
6068+ VirtualClock clock;
6069+ auto cfg = getTestConfig ();
6070+ auto srcKey = SecretKey::pseudoRandomForTesting ();
6071+ cfg.FILTERED_G_ADDRESSES = {KeyUtils::toStrKey (srcKey.getPublicKey ())};
6072+ Application::pointer app = createTestApplication (clock, cfg);
6073+
6074+ auto root = app->getRoot ();
6075+ auto src = root->create (srcKey, 1000000000 );
6076+
6077+ // Source is initially filtered
6078+ auto acc = getAccount (" acc" );
6079+ auto tx = src.tx ({createAccount (acc.getPublicKey (), 1 )});
6080+ REQUIRE (app->getHerder ().recvTransaction (tx, false ).code ==
6081+ TransactionQueue::AddResultCode::ADD_STATUS_FILTERED );
6082+
6083+ // Clear the filter via empty accountids
6084+ auto result = app->getCommandHandler ().manualCmd (
6085+ " banaccounts?accountids=" );
6086+ REQUIRE (result.find (" filtered accounts cleared" ) != std::string::npos);
6087+
6088+ // Resubmit the same transaction — it should now be accepted
6089+ REQUIRE (app->getHerder ().recvTransaction (tx, false ).code ==
6090+ TransactionQueue::AddResultCode::ADD_STATUS_PENDING );
6091+ }
6092+
6093+ SECTION (" multiple accounts" )
6094+ {
6095+ VirtualClock clock;
6096+ auto cfg = getTestConfig ();
6097+ cfg.FILTERED_G_ADDRESSES = {};
6098+ Application::pointer app = createTestApplication (clock, cfg);
6099+
6100+ auto root = app->getRoot ();
6101+ auto key1 = SecretKey::pseudoRandomForTesting ();
6102+ auto key2 = SecretKey::pseudoRandomForTesting ();
6103+ auto src1 = root->create (key1, 1000000000 );
6104+ auto src2 = root->create (key2, 1000000000 );
6105+
6106+ auto addr1 = KeyUtils::toStrKey (key1.getPublicKey ());
6107+ auto addr2 = KeyUtils::toStrKey (key2.getPublicKey ());
6108+
6109+ auto result = app->getCommandHandler ().manualCmd (
6110+ " banaccounts?accountids=" + addr1 + " ," + addr2);
6111+ REQUIRE (result.find (" \" count\" : 2" ) != std::string::npos);
6112+
6113+ auto acc = getAccount (" acc" );
6114+ auto tx1 = src1.tx ({createAccount (acc.getPublicKey (), 1 )});
6115+ REQUIRE (app->getHerder ().recvTransaction (tx1, false ).code ==
6116+ TransactionQueue::AddResultCode::ADD_STATUS_FILTERED );
6117+
6118+ auto acc2 = getAccount (" acc2" );
6119+ auto tx2 = src2.tx ({createAccount (acc2.getPublicKey (), 1 )});
6120+ REQUIRE (app->getHerder ().recvTransaction (tx2, false ).code ==
6121+ TransactionQueue::AddResultCode::ADD_STATUS_FILTERED );
6122+ }
6123+
6124+ SECTION (" invalid address returns error" )
6125+ {
6126+ VirtualClock clock;
6127+ auto cfg = getTestConfig ();
6128+ cfg.FILTERED_G_ADDRESSES = {};
6129+ Application::pointer app = createTestApplication (clock, cfg);
6130+
6131+ auto result = app->getCommandHandler ().manualCmd (
6132+ " banaccounts?accountids=NOT_A_VALID_ADDRESS" );
6133+ REQUIRE (result.find (" invalid address" ) != std::string::npos);
6134+ }
6135+
6136+ SECTION (" no accountids lists current filter" )
6137+ {
6138+ VirtualClock clock;
6139+ auto cfg = getTestConfig ();
6140+ auto srcKey = SecretKey::pseudoRandomForTesting ();
6141+ auto addr = KeyUtils::toStrKey (srcKey.getPublicKey ());
6142+ cfg.FILTERED_G_ADDRESSES = {addr};
6143+ Application::pointer app = createTestApplication (clock, cfg);
6144+
6145+ // With no accountids param, should list the current filter
6146+ auto result = app->getCommandHandler ().manualCmd (" banaccounts" );
6147+ REQUIRE (result.find (" filteredAccounts" ) != std::string::npos);
6148+ REQUIRE (result.find (addr) != std::string::npos);
6149+
6150+ // Set two accounts, then list again
6151+ auto key2 = SecretKey::pseudoRandomForTesting ();
6152+ auto addr2 = KeyUtils::toStrKey (key2.getPublicKey ());
6153+ app->getCommandHandler ().manualCmd (
6154+ " banaccounts?accountids=" + addr + " ," + addr2);
6155+
6156+ result = app->getCommandHandler ().manualCmd (" banaccounts" );
6157+ REQUIRE (result.find (addr) != std::string::npos);
6158+ REQUIRE (result.find (addr2) != std::string::npos);
6159+
6160+ // Clear and verify empty list
6161+ app->getCommandHandler ().manualCmd (" banaccounts?accountids=" );
6162+ result = app->getCommandHandler ().manualCmd (" banaccounts" );
6163+ REQUIRE (result.find (" filteredAccounts" ) != std::string::npos);
6164+ REQUIRE (result.find (addr) == std::string::npos);
6165+ }
6166+
6167+ SECTION (" fee-bump with banned fee source is rejected" )
6168+ {
6169+ VirtualClock clock;
6170+ auto cfg = getTestConfig ();
6171+ cfg.FILTERED_G_ADDRESSES = {};
6172+ Application::pointer app = createTestApplication (clock, cfg);
6173+
6174+ auto root = app->getRoot ();
6175+ auto filteredKey = SecretKey::pseudoRandomForTesting ();
6176+ auto feeSource = root->create (filteredKey, 1000000000 );
6177+ auto feeSourceAcct = TestAccount{*app, filteredKey};
6178+
6179+ auto addr = KeyUtils::toStrKey (filteredKey.getPublicKey ());
6180+ app->getCommandHandler ().manualCmd (" banaccounts?accountids=" + addr);
6181+
6182+ auto innerTx = root->tx ({payment (root->getPublicKey (), 1 )});
6183+ auto fb = feeBump (*app, feeSourceAcct, innerTx, 200 );
6184+
6185+ REQUIRE (app->getHerder ().recvTransaction (fb, false ).code ==
6186+ TransactionQueue::AddResultCode::ADD_STATUS_FILTERED );
6187+ }
6188+ }
6189+
60336190// Test that Herder updates the scphistory table with additional messages from
60346191// ledger `n-1` when closing ledger `n`
60356192TEST_CASE (" SCP message capture from previous ledger" , " [herder]" )
0 commit comments