Skip to content

Commit bb195c4

Browse files
Merge pull request #447 from stellar/txEndpointForce
Add `force` flag to tx endpoint
2 parents e8b5164 + fb91fd8 commit bb195c4

10 files changed

Lines changed: 133 additions & 22 deletions

File tree

docs/software/commands.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,12 @@ Most commands return their results in JSON format.
364364
The network is under high load and the fee is too low.
365365
* "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.
366366

367+
Optional parameters:
368+
* `force=true` - bypasses banned account filtering (see `banaccounts`),
369+
allowing the transaction into the mempool even if its source account or
370+
fee source is on the ban list. Other filtering (operation type, Soroban
371+
key filtering) still applies. Example: `tx?blob=Base64&force=true`
372+
367373
* **upgrades**
368374
* `upgrades?mode=get`<br>
369375
Retrieves the currently configured upgrade settings.<br>

src/herder/Herder.h

Lines changed: 3 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;

src/herder/HerderImpl.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -612,7 +612,8 @@ HerderImpl::emitEnvelope(SCPEnvelope const& envelope)
612612
}
613613

614614
TransactionQueue::AddResult
615-
HerderImpl::recvTransaction(TransactionFrameBasePtr tx, bool submittedFromSelf
615+
HerderImpl::recvTransaction(TransactionFrameBasePtr tx, bool submittedFromSelf,
616+
bool force
616617
#ifdef BUILD_TESTS
617618
,
618619
bool isLoadgenTx
@@ -644,7 +645,7 @@ HerderImpl::recvTransaction(TransactionFrameBasePtr tx, bool submittedFromSelf
644645
}
645646
else if (!tx->isSoroban())
646647
{
647-
result = mTransactionQueue.tryAdd(tx, submittedFromSelf
648+
result = mTransactionQueue.tryAdd(tx, submittedFromSelf, force
648649
#ifdef BUILD_TESTS
649650
,
650651
isLoadgenTx
@@ -653,7 +654,7 @@ HerderImpl::recvTransaction(TransactionFrameBasePtr tx, bool submittedFromSelf
653654
}
654655
else if (mSorobanTransactionQueue)
655656
{
656-
result = mSorobanTransactionQueue->tryAdd(tx, submittedFromSelf
657+
result = mSorobanTransactionQueue->tryAdd(tx, submittedFromSelf, force
657658
#ifdef BUILD_TESTS
658659
,
659660
isLoadgenTx

src/herder/HerderImpl.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,11 @@ class HerderImpl : public Herder
101101
#ifdef BUILD_TESTS
102102
TransactionQueue::AddResult
103103
recvTransaction(TransactionFrameBasePtr tx, bool submittedFromSelf,
104-
bool isLoadgenTx = false) override;
104+
bool force = false, bool isLoadgenTx = false) override;
105105
#else
106-
TransactionQueue::AddResult
107-
recvTransaction(TransactionFrameBasePtr tx,
108-
bool submittedFromSelf) override;
106+
TransactionQueue::AddResult recvTransaction(TransactionFrameBasePtr tx,
107+
bool submittedFromSelf,
108+
bool force = false) override;
109109
#endif
110110

111111
EnvelopeStatus recvSCPEnvelope(SCPEnvelope const& envelope) override;

src/herder/TransactionQueue.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,8 @@ TransactionQueue::sourceAccountPending(AccountID const& accountID) const
315315
TransactionQueue::AddResult
316316
TransactionQueue::canAdd(
317317
TransactionFrameBasePtr tx, AccountStates::iterator& stateIter,
318-
std::vector<std::pair<TransactionFrameBasePtr, bool>>& txsToEvict
318+
std::vector<std::pair<TransactionFrameBasePtr, bool>>& txsToEvict,
319+
bool force
319320
#ifdef BUILD_TESTS
320321
,
321322
bool isLoadgenTx
@@ -342,7 +343,7 @@ TransactionQueue::canAdd(
342343
mQueueMetrics->mTxsFilteredDueToFootprintKeys.inc();
343344
return AddResult(TransactionQueue::AddResultCode::ADD_STATUS_FILTERED);
344345
}
345-
if (!tx->validateAccountFilterForFlooding(mFilteredAccounts))
346+
if (!force && !tx->validateAccountFilterForFlooding(mFilteredAccounts))
346347
{
347348
mQueueMetrics->mTxsFilteredDueToAccountKeys.inc();
348349
return AddResult(TransactionQueue::AddResultCode::ADD_STATUS_FILTERED);
@@ -669,7 +670,8 @@ TransactionQueue::findAllAssetPairsInvolvedInPaymentLoops(
669670
}
670671

671672
TransactionQueue::AddResult
672-
TransactionQueue::tryAdd(TransactionFrameBasePtr tx, bool submittedFromSelf
673+
TransactionQueue::tryAdd(TransactionFrameBasePtr tx, bool submittedFromSelf,
674+
bool force
673675
#ifdef BUILD_TESTS
674676
,
675677
bool isLoadgenTx
@@ -687,7 +689,7 @@ TransactionQueue::tryAdd(TransactionFrameBasePtr tx, bool submittedFromSelf
687689
AccountStates::iterator stateIter;
688690

689691
std::vector<std::pair<TransactionFrameBasePtr, bool>> txsToEvict;
690-
auto res = canAdd(tx, stateIter, txsToEvict
692+
auto res = canAdd(tx, stateIter, txsToEvict, force
691693
#ifdef BUILD_TESTS
692694
,
693695
isLoadgenTx

src/herder/TransactionQueue.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,10 @@ class TransactionQueue
138138

139139
#ifdef BUILD_TESTS
140140
AddResult tryAdd(TransactionFrameBasePtr tx, bool submittedFromSelf,
141-
bool isLoadgenTx = false);
141+
bool force = false, bool isLoadgenTx = false);
142142
#else
143-
AddResult tryAdd(TransactionFrameBasePtr tx, bool submittedFromSelf);
143+
AddResult tryAdd(TransactionFrameBasePtr tx, bool submittedFromSelf,
144+
bool force = false);
144145
#endif
145146
void removeApplied(Transactions const& txs);
146147
// Ban transactions that are no longer valid or have insufficient fee;
@@ -274,11 +275,12 @@ class TransactionQueue
274275
TransactionQueue::AddResult
275276
canAdd(TransactionFrameBasePtr tx, AccountStates::iterator& stateIter,
276277
std::vector<std::pair<TransactionFrameBasePtr, bool>>& txsToEvict,
277-
bool isLoadgenTx = false);
278+
bool force = false, bool isLoadgenTx = false);
278279
#else
279280
TransactionQueue::AddResult
280281
canAdd(TransactionFrameBasePtr tx, AccountStates::iterator& stateIter,
281-
std::vector<std::pair<TransactionFrameBasePtr, bool>>& txsToEvict);
282+
std::vector<std::pair<TransactionFrameBasePtr, bool>>& txsToEvict,
283+
bool force = false);
282284
#endif
283285

284286
void releaseFeeMaybeEraseAccountState(TransactionFrameBasePtr tx);

src/main/CommandHandler.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1145,6 +1145,7 @@ CommandHandler::tx(std::string const& params, std::string& retStr)
11451145
std::map<std::string, std::string> paramMap;
11461146
http::server::server::parseParams(params, paramMap);
11471147
std::string blob = paramMap["blob"];
1148+
bool force = paramMap["force"] == "true";
11481149

11491150
if (!blob.empty())
11501151
{
@@ -1168,7 +1169,7 @@ CommandHandler::tx(std::string const& params, std::string& retStr)
11681169
{
11691170
// Add it to our current set and make sure it is valid.
11701171
auto addResult =
1171-
mApp.getHerder().recvTransaction(transaction, true);
1172+
mApp.getHerder().recvTransaction(transaction, true, force);
11721173

11731174
root["status"] = TX_STATUS_STRING[static_cast<int>(addResult.code)];
11741175
if (addResult.code ==

src/main/test/BannedAccountsPersistorTests.cpp

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,64 @@ TEST_CASE("FILTERED_G_ADDRESSES migration", "[banaccounts]")
158158

159159
TEST_CASE("banaccounts HTTP command with persistence", "[banaccounts]")
160160
{
161+
SECTION("force flag bypasses banned account filtering")
162+
{
163+
VirtualClock clock;
164+
auto cfg = getTestConfig();
165+
cfg.FILTERED_G_ADDRESSES = {};
166+
Application::pointer app = createTestApplication(clock, cfg);
167+
168+
auto root = app->getRoot();
169+
auto srcKey = SecretKey::pseudoRandomForTesting();
170+
auto src = root->create(srcKey, 1000000000);
171+
172+
// Ban the source account
173+
auto addr = KeyUtils::toStrKey(srcKey.getPublicKey());
174+
app->getCommandHandler().manualCmd("banaccounts?accountids=" + addr);
175+
176+
auto acc = getAccount("forceAcc");
177+
auto tx = src.tx({createAccount(acc.getPublicKey(), 1)});
178+
179+
// Without force: filtered
180+
REQUIRE(
181+
app->getHerder().recvTransaction(tx, false, /*force=*/false).code ==
182+
TransactionQueue::AddResultCode::ADD_STATUS_FILTERED);
183+
184+
// With force: bypasses account filter
185+
REQUIRE(
186+
app->getHerder().recvTransaction(tx, false, /*force=*/true).code ==
187+
TransactionQueue::AddResultCode::ADD_STATUS_PENDING);
188+
}
189+
190+
SECTION("force flag bypasses ban for fee-bump with banned fee source")
191+
{
192+
VirtualClock clock;
193+
auto cfg = getTestConfig();
194+
cfg.FILTERED_G_ADDRESSES = {};
195+
Application::pointer app = createTestApplication(clock, cfg);
196+
197+
auto root = app->getRoot();
198+
auto filteredKey = SecretKey::pseudoRandomForTesting();
199+
auto feeSourceAcct = root->create(filteredKey, 1000000000);
200+
201+
// Ban the fee source account
202+
auto addr = KeyUtils::toStrKey(filteredKey.getPublicKey());
203+
app->getCommandHandler().manualCmd("banaccounts?accountids=" + addr);
204+
205+
auto innerTx = root->tx({payment(root->getPublicKey(), 1)});
206+
auto fb = feeBump(*app, feeSourceAcct, innerTx, 200);
207+
208+
// Without force: filtered
209+
REQUIRE(
210+
app->getHerder().recvTransaction(fb, false, /*force=*/false).code ==
211+
TransactionQueue::AddResultCode::ADD_STATUS_FILTERED);
212+
213+
// With force: bypasses account filter
214+
REQUIRE(
215+
app->getHerder().recvTransaction(fb, false, /*force=*/true).code ==
216+
TransactionQueue::AddResultCode::ADD_STATUS_PENDING);
217+
}
218+
161219
SECTION("ban via command persists and filters transactions")
162220
{
163221
VirtualClock clock;
@@ -178,8 +236,9 @@ TEST_CASE("banaccounts HTTP command with persistence", "[banaccounts]")
178236
// Transaction from the banned source should be filtered
179237
auto acc = getAccount("acc");
180238
auto tx = src.tx({createAccount(acc.getPublicKey(), 1)});
181-
REQUIRE(app->getHerder().recvTransaction(tx, false).code ==
182-
TransactionQueue::AddResultCode::ADD_STATUS_FILTERED);
239+
REQUIRE(
240+
app->getHerder().recvTransaction(tx, false, /*force=*/false).code ==
241+
TransactionQueue::AddResultCode::ADD_STATUS_FILTERED);
183242

184243
// Verify persisted
185244
REQUIRE(app->getBannedAccountsPersistor().getBannedAccounts().size() ==

src/main/test/CommandHandlerTests.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// under the Apache License, Version 2.0. See the COPYING file at the root
33
// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0
44

5+
#include "crypto/KeyUtils.h"
56
#include "ledger/LedgerTxn.h"
67
#include "ledger/test/LedgerTestUtils.h"
78
#include "main/Application.h"
@@ -603,3 +604,41 @@ TEST_CASE("toggleoverlayonlymode", "[commandhandler]")
603604
REQUIRE(root["overlay_only_mode"].asBool() == expectedMode);
604605
}
605606
}
607+
608+
TEST_CASE("tx force flag bypasses banned account filter", "[commandhandler]")
609+
{
610+
VirtualClock clock;
611+
auto cfg = getTestConfig();
612+
cfg.FILTERED_G_ADDRESSES = {};
613+
auto app = createTestApplication(clock, cfg);
614+
auto& ch = app->getCommandHandler();
615+
616+
closeLedgerOn(*app, 2, 1, 1, 2017);
617+
618+
auto root = app->getRoot();
619+
auto srcKey = SecretKey::pseudoRandomForTesting();
620+
auto src = root->create(srcKey, 1000000000);
621+
622+
// Ban the source account
623+
auto addr = KeyUtils::toStrKey(srcKey.getPublicKey());
624+
ch.manualCmd("banaccounts?accountids=" + addr);
625+
626+
// Build a valid transaction from the banned account
627+
auto acc = getAccount("forceTestAcc");
628+
auto tx = src.tx({createAccount(acc.getPublicKey(), 1)});
629+
auto blob = decoder::encode_b64(xdr::xdr_to_opaque(tx->getEnvelope()));
630+
631+
SECTION("without force flag, tx is filtered")
632+
{
633+
std::string ret;
634+
ch.tx("?blob=" + blob, ret);
635+
REQUIRE(ret.find("FILTERED") != std::string::npos);
636+
}
637+
638+
SECTION("with force=true, tx bypasses account ban")
639+
{
640+
std::string ret;
641+
ch.tx("?blob=" + blob + "&force=true", ret);
642+
REQUIRE(ret.find("PENDING") != std::string::npos);
643+
}
644+
}

src/simulation/LoadGenerator.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1418,8 +1418,8 @@ LoadGenerator::execute(TransactionFrameBasePtr txf, LoadGenMode mode,
14181418

14191419
// Skip certain checks for pregenerated transactions
14201420
bool isPregeneratedTx = (mode == LoadGenMode::PAY_PREGENERATED);
1421-
auto addResult =
1422-
mApp.getHerder().recvTransaction(txf, true, isPregeneratedTx);
1421+
auto addResult = mApp.getHerder().recvTransaction(
1422+
txf, true, /*force=*/false, /*isLoadgenTx=*/isPregeneratedTx);
14231423
if (addResult.code != TransactionQueue::AddResultCode::ADD_STATUS_PENDING)
14241424
{
14251425

0 commit comments

Comments
 (0)