diff --git a/Builds/VisualStudio2015/RippleD.vcxproj b/Builds/VisualStudio2015/RippleD.vcxproj index 7524b11da..20dd9bceb 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj +++ b/Builds/VisualStudio2015/RippleD.vcxproj @@ -4247,6 +4247,10 @@ True True + + True + True + True True diff --git a/Builds/VisualStudio2015/RippleD.vcxproj.filters b/Builds/VisualStudio2015/RippleD.vcxproj.filters index 3fbca722e..03fc1ae80 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2015/RippleD.vcxproj.filters @@ -5037,6 +5037,9 @@ test\app + + test\app + test\app diff --git a/src/ripple/app/consensus/RCLConsensus.cpp b/src/ripple/app/consensus/RCLConsensus.cpp index b7e448395..55323ff86 100644 --- a/src/ripple/app/consensus/RCLConsensus.cpp +++ b/src/ripple/app/consensus/RCLConsensus.cpp @@ -155,7 +155,6 @@ RCLConsensus::relay(RCLCxTx const& tx) if (app_.getHashRouter().shouldRelay(tx.id())) { auto const slice = tx.tx_.slice(); - protocol::TMTransaction msg; msg.set_rawtransaction(slice.data(), slice.size()); msg.set_status(protocol::tsNEW); @@ -479,6 +478,12 @@ RCLConsensus::doAccept( SerialIter sit(it.second.tx().tx_.slice()); auto txn = std::make_shared(sit); + + // Disputed pseudo-transactions that were not accepted + // can't be succesfully applied in the next ledger + if (isPseudoTx(*txn)) + continue; + retriableTxs.insert(txn); anyDisputes = true; diff --git a/src/ripple/app/tx/impl/applySteps.cpp b/src/ripple/app/tx/impl/applySteps.cpp index 1c0f1ac0e..0809f6fa8 100644 --- a/src/ripple/app/tx/impl/applySteps.cpp +++ b/src/ripple/app/tx/impl/applySteps.cpp @@ -77,10 +77,10 @@ invoke_preclaim(PreclaimContext const& ctx) // list one, preflight will have already a flagged a failure. auto const id = ctx.tx.getAccountID(sfAccount); auto const baseFee = T::calculateBaseFee(ctx); - TER result; + if (id != zero) { - result = T::checkSeq(ctx); + TER result = T::checkSeq(ctx); if (result != tesSUCCESS) return { result, baseFee }; @@ -95,17 +95,9 @@ invoke_preclaim(PreclaimContext const& ctx) if (result != tesSUCCESS) return { result, baseFee }; - result = T::preclaim(ctx); - - if (result != tesSUCCESS) - return{ result, baseFee }; - } - else - { - result = tesSUCCESS; } - return { tesSUCCESS, baseFee }; + return{ T::preclaim(ctx), baseFee }; } static diff --git a/src/ripple/protocol/STTx.h b/src/ripple/protocol/STTx.h index 9e196dbac..6ab0035bb 100644 --- a/src/ripple/protocol/STTx.h +++ b/src/ripple/protocol/STTx.h @@ -167,6 +167,9 @@ bool passesLocalChecks (STObject const& st, std::string&); std::shared_ptr sterilize (STTx const& stx); +/** Check whether a transaction is a pseudo-transaction */ +bool isPseudoTx(STObject const& tx); + } // ripple #endif diff --git a/src/ripple/protocol/impl/STTx.cpp b/src/ripple/protocol/impl/STTx.cpp index 3a92127e1..29d639435 100644 --- a/src/ripple/protocol/impl/STTx.cpp +++ b/src/ripple/protocol/impl/STTx.cpp @@ -504,6 +504,11 @@ bool passesLocalChecks (STObject const& st, std::string& reason) return false; } + if (isPseudoTx(st)) + { + reason = "Cannot submit pseudo transactions."; + return false; + } return true; } @@ -516,4 +521,14 @@ sterilize (STTx const& stx) return std::make_shared(std::ref(sit)); } +bool +isPseudoTx(STObject const& tx) +{ + auto t = tx[~sfTransactionType]; + if (!t) + return false; + auto tt = static_cast(*t); + return tt == ttAMENDMENT || tt == ttFEE; +} + } // ripple diff --git a/src/test/app/PseudoTx_test.cpp b/src/test/app/PseudoTx_test.cpp new file mode 100644 index 000000000..541a085d5 --- /dev/null +++ b/src/test/app/PseudoTx_test.cpp @@ -0,0 +1,114 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2017 Ripple Labs Inc. + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include +#include + +namespace ripple { +namespace test { + +struct PseudoTx_test : public beast::unit_test::suite +{ + std::vector + getPseudoTxs(std::uint32_t seq) + { + std::vector res; + + res.emplace_back(STTx(ttFEE, [&](auto& obj) { + obj[sfAccount] = AccountID(); + obj[sfLedgerSequence] = seq; + obj[sfBaseFee] = 0; + obj[sfReserveBase] = 0; + obj[sfReserveIncrement] = 0; + obj[sfReferenceFeeUnits] = 0; + })); + + res.emplace_back(STTx(ttAMENDMENT, [&](auto& obj) { + obj.setAccountID(sfAccount, AccountID()); + obj.setFieldH256(sfAmendment, uint256(2)); + obj.setFieldU32(sfLedgerSequence, seq); + })); + + return res; + } + + std::vector + getRealTxs() + { + std::vector res; + + res.emplace_back(STTx(ttACCOUNT_SET, [&](auto& obj) { + obj[sfAccount] = AccountID(1); + })); + + res.emplace_back(STTx(ttPAYMENT, [&](auto& obj) { + obj.setAccountID(sfAccount, AccountID(2)); + obj.setAccountID(sfDestination, AccountID(3)); + })); + + return res; + } + + void + testPrevented() + { + using namespace jtx; + Env env(*this); + + for (auto const& stx : getPseudoTxs(env.closed()->seq() + 1)) + { + std::string reason; + BEAST_EXPECT(isPseudoTx(stx)); + BEAST_EXPECT(!passesLocalChecks(stx, reason)); + BEAST_EXPECT(reason == "Cannot submit pseudo transactions."); + env.app().openLedger().modify( + [&](OpenView& view, beast::Journal j) { + auto const result = + ripple::apply(env.app(), view, stx, tapNONE, j); + BEAST_EXPECT(!result.second && result.first == temINVALID); + return result.second; + }); + } + } + + void + testAllowed() + { + for (auto const& stx : getRealTxs()) + { + std::string reason; + BEAST_EXPECT(!isPseudoTx(stx)); + BEAST_EXPECT(passesLocalChecks(stx, reason)); + } + } + + void + run() override + { + testPrevented(); + testAllowed(); + } +}; + +BEAST_DEFINE_TESTSUITE(PseudoTx, app, ripple); + +} // test +} // ripple diff --git a/src/test/app/TxQ_test.cpp b/src/test/app/TxQ_test.cpp index 26c43a295..8adb6cca6 100644 --- a/src/test/app/TxQ_test.cpp +++ b/src/test/app/TxQ_test.cpp @@ -35,6 +35,13 @@ #include namespace ripple { + +namespace detail { +extern +std::vector +supportedAmendments (); +} + namespace test { class TxQ_test : public beast::unit_test::suite @@ -96,7 +103,8 @@ class TxQ_test : public beast::unit_test::suite static std::unique_ptr - makeConfig(std::map extra = {}) + makeConfig(std::map extraTxQ = {}, + std::map extraVoting = {}) { auto p = test::jtx::envconfig(); auto& section = p->section("transaction_queue"); @@ -105,8 +113,24 @@ class TxQ_test : public beast::unit_test::suite section.set("max_ledger_counts_to_store", "100"); section.set("retry_sequence_percent", "25"); section.set("zero_basefee_transaction_feelevel", "100000000000"); - for (auto const& value : extra) + + for (auto const& value : extraTxQ) section.set(value.first, value.second); + + // Some tests specify different fee settings that are enabled by + // a FeeVote + if (!extraVoting.empty()) + { + + auto& votingSection = p->section("voting"); + for (auto const & value : extraVoting) + { + votingSection.set(value.first, value.second); + } + + // In order for the vote to occur, we must run as a validator + p->section("validation_seed").legacy("shUwVw52ofnCUX5m7kPTKzJdr4HEH"); + } return p; } @@ -114,37 +138,22 @@ class TxQ_test : public beast::unit_test::suite initFee(jtx::Env& env, std::size_t expectedInLedger, std::uint32_t base, std::uint32_t units, std::uint32_t reserve, std::uint32_t increment) { - // Change the reserve fee so we can create an account - // with a lower balance. - STTx feeTx(ttFEE, - [&](auto& obj) - { - obj[sfAccount] = AccountID(); - obj[sfLedgerSequence] = env.current()->info().seq; - obj[sfBaseFee] = base; - obj[sfReferenceFeeUnits] = units; - obj[sfReserveBase] = reserve; - obj[sfReserveIncrement] = increment; - }); + // Run past the flag ledger so that a Fee change vote occurs and + // lowers the reserve fee. This will allow creating accounts with lower + // balances. + for(auto i = env.current()->seq(); i <= 257; ++i) + env.close(); - auto const changed = env.app().openLedger().modify( - [&](OpenView& view, beast::Journal j) - { - auto const result = ripple::apply( - env.app(), view, feeTx, - tapNONE, j); - BEAST_EXPECT(result.second && result.first == - tesSUCCESS); - return result.second; - }); - BEAST_EXPECT(changed); // Pad a couple of txs to keep the median at the default env(noop(env.master)); env(noop(env.master)); + // Close the ledger with a delay to force the TxQ stats // to stay at the default. env.close(env.now() + 5s, 10000ms); - checkMetrics(env, 0, boost::none, 0, expectedInLedger, 256); + checkMetrics(env, 0, + 2 * (ripple::detail::supportedAmendments().size() + 1), + 0, expectedInLedger, 256); auto const fees = env.current()->fees(); BEAST_EXPECT(fees.base == base); BEAST_EXPECT(fees.units == units); @@ -694,7 +703,11 @@ public: { using namespace jtx; - Env env(*this, makeConfig({ { "minimum_txn_in_ledger_standalone", "3" } }), + Env env( + *this, + makeConfig( + {{"minimum_txn_in_ledger_standalone", "3"}}, + {{"account_reserve", "200"}, {"owner_reserve", "50"}}), features(featureFeeEscalation)); auto alice = Account("alice"); @@ -709,15 +722,17 @@ public: checkMetrics(env, 0, boost::none, 0, 3, 256); initFee(env, 3, 10, 10, 200, 50); + auto const initQueueMax = + 2 * (ripple::detail::supportedAmendments().size() + 1); // Create several accounts while the fee is cheap so they all apply. env.fund(drops(2000), noripple(alice)); env.fund(XRP(500000), noripple(bob, charlie, daria)); - checkMetrics(env, 0, boost::none, 4, 3, 256); + checkMetrics(env, 0, initQueueMax, 4, 3, 256); // Alice - price starts exploding: held env(noop(alice), queued); - checkMetrics(env, 1, boost::none, 4, 3, 256); + checkMetrics(env, 1, initQueueMax, 4, 3, 256); auto aliceSeq = env.seq(alice); auto bobSeq = env.seq(bob); @@ -726,31 +741,31 @@ public: // Alice - try to queue a second transaction, but leave a gap env(noop(alice), seq(aliceSeq + 2), fee(100), ter(terPRE_SEQ)); - checkMetrics(env, 1, boost::none, 4, 3, 256); + checkMetrics(env, 1, initQueueMax, 4, 3, 256); // Alice - queue a second transaction. Yay. env(noop(alice), seq(aliceSeq + 1), fee(13), queued); - checkMetrics(env, 2, boost::none, 4, 3, 256); + checkMetrics(env, 2, initQueueMax, 4, 3, 256); // Alice - queue a third transaction. Yay. env(noop(alice), seq(aliceSeq + 2), fee(17), queued); - checkMetrics(env, 3, boost::none, 4, 3, 256); + checkMetrics(env, 3, initQueueMax, 4, 3, 256); // Bob - queue a transaction env(noop(bob), queued); - checkMetrics(env, 4, boost::none, 4, 3, 256); + checkMetrics(env, 4, initQueueMax, 4, 3, 256); // Bob - queue a second transaction env(noop(bob), seq(bobSeq + 1), fee(50), queued); - checkMetrics(env, 5, boost::none, 4, 3, 256); + checkMetrics(env, 5, initQueueMax, 4, 3, 256); // Charlie - queue a transaction, with a higher fee // than default env(noop(charlie), fee(15), queued); - checkMetrics(env, 6, boost::none, 4, 3, 256); + checkMetrics(env, 6, initQueueMax, 4, 3, 256); BEAST_EXPECT(env.seq(alice) == aliceSeq); BEAST_EXPECT(env.seq(bob) == bobSeq); @@ -1148,9 +1163,12 @@ public: { using namespace jtx; - Env env(*this, - makeConfig({ { "minimum_txn_in_ledger_standalone", "3" } }), - features(featureFeeEscalation)); + Env env( + *this, + makeConfig( + {{"minimum_txn_in_ledger_standalone", "3"}}, + {{"account_reserve", "200"}, {"owner_reserve", "50"}}), + features(featureFeeEscalation)); auto alice = Account("alice"); auto bob = Account("bob"); @@ -1158,18 +1176,20 @@ public: auto queued = ter(terQUEUED); initFee(env, 3, 10, 10, 200, 50); + auto const initQueueMax = + 2 * (ripple::detail::supportedAmendments().size() + 1); BEAST_EXPECT(env.current()->fees().base == 10); - checkMetrics(env, 0, boost::none, 0, 3, 256); + checkMetrics(env, 0, initQueueMax, 0, 3, 256); env.fund(drops(5000), noripple(alice)); env.fund(XRP(50000), noripple(bob)); - checkMetrics(env, 0, boost::none, 2, 3, 256); + checkMetrics(env, 0, initQueueMax, 2, 3, 256); auto USD = bob["USD"]; env(offer(alice, USD(5000), drops(5000)), require(owners(alice, 1))); - checkMetrics(env, 0, boost::none, 3, 3, 256); + checkMetrics(env, 0, initQueueMax, 3, 3, 256); env.close(); checkMetrics(env, 0, 6, 0, 3, 256); diff --git a/src/test/unity/app_test_unity.cpp b/src/test/unity/app_test_unity.cpp index 72c9d4b0c..232bad362 100644 --- a/src/test/unity/app_test_unity.cpp +++ b/src/test/unity/app_test_unity.cpp @@ -49,3 +49,4 @@ #include #include #include +#include