mirror of
https://github.com/Xahau/xahaud.git
synced 2025-11-27 22:15:49 +00:00
Refactor jtx::Env:
These changes eliminate the Env's OpenLedger member and make transactions go through the Application associated with each instance of the Env, making the unit tests follow a code path closer to the production code path. * Add Env::open() for open ledger * Add Env::now() * Rename to Env::current() * Inject ManualTimeKeeper in Env Application * Make Config mutable * Move setupConfigForUnitTests * Launch Env Application thread * Use Application ledgers in Env * Adjust Application clock on ledger close * Adjust close time for close resolution * Scrub obsolete clock types * Enable features via Env ctor * Make Env::master Account object global * Cache SSL context (performance) * Cache master wallet keys in Ledger ctor (performance)
This commit is contained in:
@@ -3628,6 +3628,10 @@
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ripple\shamap\TreeNodeCache.h">
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ripple\test\impl\ManualTimeKeeper.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ripple\test\jtx.h">
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\test\jtx\Account.h">
|
||||
@@ -3794,6 +3798,8 @@
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\test\jtx\utility.h">
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\test\ManualTimeKeeper.h">
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ripple\test\mao\impl\Net.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
|
||||
@@ -412,6 +412,9 @@
|
||||
<Filter Include="ripple\test">
|
||||
<UniqueIdentifier>{B25F5854-84AE-1CBD-DFFC-6515DD055652}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="ripple\test\impl">
|
||||
<UniqueIdentifier>{CF7F0C3F-3D61-7764-BA8B-5FF38018425C}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="ripple\test\jtx">
|
||||
<UniqueIdentifier>{A21A3B94-5C44-3746-4F10-6FF8FF990CE3}</UniqueIdentifier>
|
||||
</Filter>
|
||||
@@ -4284,6 +4287,9 @@
|
||||
<ClInclude Include="..\..\src\ripple\shamap\TreeNodeCache.h">
|
||||
<Filter>ripple\shamap</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ripple\test\impl\ManualTimeKeeper.cpp">
|
||||
<Filter>ripple\test\impl</Filter>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ripple\test\jtx.h">
|
||||
<Filter>ripple\test</Filter>
|
||||
</ClInclude>
|
||||
@@ -4458,6 +4464,9 @@
|
||||
<ClInclude Include="..\..\src\ripple\test\jtx\utility.h">
|
||||
<Filter>ripple\test\jtx</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\test\ManualTimeKeeper.h">
|
||||
<Filter>ripple\test</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ripple\test\mao\impl\Net.cpp">
|
||||
<Filter>ripple\test\mao\impl</Filter>
|
||||
</ClCompile>
|
||||
|
||||
@@ -5,8 +5,6 @@ VisualStudioVersion = 14.0.23107.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RippleD", "RippleD.vcxproj", "{26B7D9AC-1A80-8EF8-6703-D061F1BECB75}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{CF990ABA-EAE4-47AB-BF5E-0BCBB95CE325}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
debug.classic|x64 = debug.classic|x64
|
||||
|
||||
@@ -179,7 +179,7 @@ Ledger::Ledger (create_genesis_t, Config const& config, Family& family)
|
||||
info_.seq = 1;
|
||||
info_.drops = SYSTEM_CURRENCY_START;
|
||||
info_.closeTimeResolution = ledgerDefaultTimeResolution;
|
||||
auto const id = calcAccountID(
|
||||
static auto const id = calcAccountID(
|
||||
generateKeyPair(KeyType::secp256k1,
|
||||
generateSeed("masterpassphrase")).first);
|
||||
auto const sle = std::make_shared<SLE>(keylet::account(id));
|
||||
|
||||
@@ -286,13 +286,13 @@ private:
|
||||
};
|
||||
|
||||
public:
|
||||
std::unique_ptr<Config const> config_;
|
||||
std::unique_ptr<Config> config_;
|
||||
std::unique_ptr<Logs> logs_;
|
||||
std::unique_ptr<TimeKeeper> timeKeeper_;
|
||||
|
||||
beast::Journal m_journal;
|
||||
Application::MutexType m_masterMutex;
|
||||
|
||||
std::unique_ptr<TimeKeeper> timeKeeper_;
|
||||
|
||||
// Required by the SHAMapStore
|
||||
TransactionMaster m_txMaster;
|
||||
|
||||
@@ -363,18 +363,17 @@ public:
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
ApplicationImp (
|
||||
std::unique_ptr<Config const> config,
|
||||
std::unique_ptr<Logs> logs)
|
||||
std::unique_ptr<Config> config,
|
||||
std::unique_ptr<Logs> logs,
|
||||
std::unique_ptr<TimeKeeper> timeKeeper)
|
||||
: RootStoppable ("Application")
|
||||
, BasicApp (numberOfThreads(*config))
|
||||
, config_ (std::move(config))
|
||||
, logs_ (std::move(logs))
|
||||
, timeKeeper_ (std::move(timeKeeper))
|
||||
|
||||
, m_journal (logs_->journal("Application"))
|
||||
|
||||
, timeKeeper_ (make_TimeKeeper(
|
||||
logs_->journal("TimeKeeper")))
|
||||
|
||||
, m_txMaster (*this)
|
||||
|
||||
, m_nodeStoreScheduler (*this)
|
||||
@@ -518,8 +517,8 @@ public:
|
||||
return *logs_;
|
||||
}
|
||||
|
||||
Config const&
|
||||
config() const override
|
||||
Config&
|
||||
config() override
|
||||
{
|
||||
return *config_;
|
||||
}
|
||||
@@ -691,6 +690,12 @@ public:
|
||||
return *openLedger_;
|
||||
}
|
||||
|
||||
OpenLedger const&
|
||||
openLedger() const override
|
||||
{
|
||||
return *openLedger_;
|
||||
}
|
||||
|
||||
Overlay& overlay () override
|
||||
{
|
||||
return *m_overlay;
|
||||
@@ -1676,21 +1681,13 @@ Application::Application ()
|
||||
|
||||
std::unique_ptr<Application>
|
||||
make_Application (
|
||||
std::unique_ptr<Config const> config,
|
||||
std::unique_ptr<Logs> logs)
|
||||
std::unique_ptr<Config> config,
|
||||
std::unique_ptr<Logs> logs,
|
||||
std::unique_ptr<TimeKeeper> timeKeeper)
|
||||
{
|
||||
return std::make_unique<ApplicationImp> (
|
||||
std::move(config), std::move(logs));
|
||||
}
|
||||
|
||||
void
|
||||
setupConfigForUnitTests (Config& config)
|
||||
{
|
||||
config.overwrite (ConfigSection::nodeDatabase (), "type", "memory");
|
||||
config.overwrite (ConfigSection::nodeDatabase (), "path", "main");
|
||||
|
||||
config.deprecatedClearSection (ConfigSection::importNodeDatabase ());
|
||||
config.legacy("database_path", "DummyForUnitTests");
|
||||
std::move(config), std::move(logs),
|
||||
std::move(timeKeeper));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ public:
|
||||
//
|
||||
|
||||
virtual Logs& logs() = 0;
|
||||
virtual Config const& config() const = 0;
|
||||
virtual Config& config() = 0;
|
||||
virtual boost::asio::io_service& getIOService () = 0;
|
||||
virtual CollectorManager& getCollectorManager () = 0;
|
||||
virtual Family& family() = 0;
|
||||
@@ -136,6 +136,7 @@ public:
|
||||
virtual PendingSaves& pendingSaves() = 0;
|
||||
virtual AccountIDCache const& accountIDCache() const = 0;
|
||||
virtual OpenLedger& openLedger() = 0;
|
||||
virtual OpenLedger const& openLedger() const = 0;
|
||||
virtual DatabaseCon& getTxnDB () = 0;
|
||||
virtual DatabaseCon& getLedgerDB () = 0;
|
||||
|
||||
@@ -156,12 +157,9 @@ public:
|
||||
|
||||
std::unique_ptr <Application>
|
||||
make_Application(
|
||||
std::unique_ptr<Config const> config,
|
||||
std::unique_ptr<Logs> logs);
|
||||
|
||||
extern
|
||||
void
|
||||
setupConfigForUnitTests (Config& config);
|
||||
std::unique_ptr<Config> config,
|
||||
std::unique_ptr<Logs> logs,
|
||||
std::unique_ptr<TimeKeeper> timeKeeper);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <ripple/basics/ThreadName.h>
|
||||
#include <ripple/core/Config.h>
|
||||
#include <ripple/core/ConfigSections.h>
|
||||
#include <ripple/core/TimeKeeper.h>
|
||||
#include <ripple/crypto/RandomNumbers.h>
|
||||
#include <ripple/json/to_string.h>
|
||||
#include <ripple/net/RPCCall.h>
|
||||
@@ -162,37 +163,6 @@ void printHelp (const po::options_description& desc)
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
static int runShutdownTests (std::unique_ptr<Config> config)
|
||||
{
|
||||
// Shutdown tests can not be part of the normal unit tests in 'runUnitTests'
|
||||
// because it needs to create and destroy an application object.
|
||||
// FIXME: we only loop once, since the Config object will get destroyed
|
||||
int const numShutdownIterations = 1; //20;
|
||||
|
||||
// Give it enough time to sync and run a bit while synced.
|
||||
std::chrono::seconds const serverUptimePerIteration (4 * 60);
|
||||
for (int i = 0; i < numShutdownIterations; ++i)
|
||||
{
|
||||
std::cerr << "\n\nStarting server. Iteration: " << i << "\n"
|
||||
<< std::endl;
|
||||
auto app = make_Application (
|
||||
std::move(config),
|
||||
std::make_unique<Logs>());
|
||||
auto shutdownApp = [&app](std::chrono::seconds sleepTime, int iteration)
|
||||
{
|
||||
std::this_thread::sleep_for (sleepTime);
|
||||
std::cerr << "\n\nStopping server. Iteration: " << iteration << "\n"
|
||||
<< std::endl;
|
||||
app->signalStop();
|
||||
};
|
||||
std::thread shutdownThread (shutdownApp, serverUptimePerIteration, i);
|
||||
setupServer(*app);
|
||||
startServer(*app);
|
||||
shutdownThread.join();
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
static int runUnitTests(
|
||||
std::string const& pattern,
|
||||
std::string const& argument)
|
||||
@@ -265,7 +235,6 @@ int run (int argc, char** argv)
|
||||
("rpc_ip", po::value <std::string> (), "Specify the IP address for RPC command. Format: <ip-address>[':'<port-number>]")
|
||||
("rpc_port", po::value <std::uint16_t> (), "Specify the port number for RPC command.")
|
||||
("standalone,a", "Run with no peers.")
|
||||
("shutdowntest", po::value <std::string> ()->implicit_value (""), "Perform shutdown tests.")
|
||||
("unittest,u", po::value <std::string> ()->implicit_value (""), "Perform unit tests.")
|
||||
("unittest-arg", po::value <std::string> ()->implicit_value (""), "Supplies argument to unit tests.")
|
||||
("parameters", po::value< vector<string> > (), "Specify comma separated parameters.")
|
||||
@@ -325,7 +294,6 @@ int run (int argc, char** argv)
|
||||
&& !vm.count ("parameters")
|
||||
&& !vm.count ("fg")
|
||||
&& !vm.count ("standalone")
|
||||
&& !vm.count ("shutdowntest")
|
||||
&& !vm.count ("unittest"))
|
||||
{
|
||||
std::string logMe = DoSustain ();
|
||||
@@ -471,13 +439,12 @@ int run (int argc, char** argv)
|
||||
}
|
||||
}
|
||||
|
||||
if (vm.count ("shutdowntest"))
|
||||
return runShutdownTests (std::move(config));
|
||||
|
||||
// No arguments. Run server.
|
||||
if (!vm.count ("parameters"))
|
||||
{
|
||||
auto logs = std::make_unique<Logs>();
|
||||
auto timeKeeper = make_TimeKeeper(
|
||||
logs->journal("TimeKeeper"));
|
||||
|
||||
if (vm.count ("quiet"))
|
||||
logs->severity (beast::Journal::kFatal);
|
||||
@@ -488,7 +455,8 @@ int run (int argc, char** argv)
|
||||
|
||||
auto app = make_Application(
|
||||
std::move(config),
|
||||
std::move (logs));
|
||||
std::move(logs),
|
||||
std::move(timeKeeper));
|
||||
setupServer (*app);
|
||||
startServer (*app);
|
||||
return 0;
|
||||
|
||||
@@ -149,8 +149,11 @@ public:
|
||||
|
||||
auto const switchoverTime = STAmountCalcSwitchovers::enableUnderflowFixCloseTime ();
|
||||
|
||||
for (auto timeDelta : {-10s, 10s}){
|
||||
NetClock::time_point const closeTime = switchoverTime + timeDelta;
|
||||
for (auto timeDelta : {
|
||||
- env.closed()->info().closeTimeResolution,
|
||||
env.closed()->info().closeTimeResolution} )
|
||||
{
|
||||
auto const closeTime = switchoverTime + timeDelta;
|
||||
STAmountCalcSwitchovers switchover (closeTime);
|
||||
env.close (closeTime);
|
||||
// Will fail without the underflow fix
|
||||
|
||||
@@ -141,7 +141,7 @@ find_paths(jtx::Env& env,
|
||||
boost::optional<STAmount> const& saSendMax = boost::none)
|
||||
{
|
||||
static int const level = 8;
|
||||
auto const& view = env.open ();
|
||||
auto const& view = env.current();
|
||||
auto cache = std::make_shared<RippleLineCache>(view);
|
||||
auto currencies = accountSourceCurrencies(src, cache, true);
|
||||
auto jvSrcCurrencies = Json::Value(Json::arrayValue);
|
||||
|
||||
@@ -148,7 +148,7 @@ struct SusPay_test : public beast::unit_test::suite
|
||||
{
|
||||
Env env(*this);
|
||||
auto T = [&env](NetClock::duration const& d)
|
||||
{ return env.clock.now() + d; };
|
||||
{ return env.now() + d; };
|
||||
env.fund(XRP(5000), "alice", "bob");
|
||||
auto const c = cond("receipt");
|
||||
// syntax
|
||||
@@ -171,7 +171,7 @@ struct SusPay_test : public beast::unit_test::suite
|
||||
Env env(*this);
|
||||
auto const alice = Account("alice");
|
||||
auto T = [&env](NetClock::duration const& d)
|
||||
{ return env.clock.now() + d; };
|
||||
{ return env.now() + d; };
|
||||
env.fund(XRP(5000), alice, "bob");
|
||||
auto const c = cond("receipt");
|
||||
auto const seq = env.seq(alice);
|
||||
@@ -192,7 +192,7 @@ struct SusPay_test : public beast::unit_test::suite
|
||||
{
|
||||
Env env(*this);
|
||||
auto T = [&env](NetClock::duration const& d)
|
||||
{ return env.clock.now() + d; };
|
||||
{ return env.now() + d; };
|
||||
env.fund(XRP(5000), "alice", "bob");
|
||||
auto const c = cond("receipt");
|
||||
// VFALCO Should we enforce this?
|
||||
@@ -224,7 +224,7 @@ struct SusPay_test : public beast::unit_test::suite
|
||||
{
|
||||
Env env(*this);
|
||||
auto T = [&env](NetClock::duration const& d)
|
||||
{ return env.clock.now() + d; };
|
||||
{ return env.now() + d; };
|
||||
env.fund(XRP(5000), "alice", "bob");
|
||||
auto const seq = env.seq("alice");
|
||||
env(lockup("alice", "alice", XRP(1000), T(S{1})));
|
||||
@@ -246,7 +246,7 @@ struct SusPay_test : public beast::unit_test::suite
|
||||
{
|
||||
Env env(*this);
|
||||
auto T = [&env](NetClock::duration const& d)
|
||||
{ return env.clock.now() + d; };
|
||||
{ return env.now() + d; };
|
||||
env.fund(XRP(5000), "alice", "bob", "carol");
|
||||
auto const c = cond("receipt");
|
||||
auto const seq = env.seq("alice");
|
||||
@@ -272,7 +272,7 @@ struct SusPay_test : public beast::unit_test::suite
|
||||
{
|
||||
Env env(*this);
|
||||
auto T = [&env](NetClock::duration const& d)
|
||||
{ return env.clock.now() + d; };
|
||||
{ return env.now() + d; };
|
||||
env.fund(XRP(5000), "alice", "bob", "carol");
|
||||
auto const c = cond("receipt");
|
||||
auto const seq = env.seq("alice");
|
||||
@@ -289,7 +289,7 @@ struct SusPay_test : public beast::unit_test::suite
|
||||
{
|
||||
Env env(*this);
|
||||
auto T = [&env](NetClock::duration const& d)
|
||||
{ return env.clock.now() + d; };
|
||||
{ return env.now() + d; };
|
||||
env.fund(XRP(5000), "alice", "bob", "carol");
|
||||
env.close();
|
||||
auto const c = cond("receipt");
|
||||
@@ -308,7 +308,7 @@ struct SusPay_test : public beast::unit_test::suite
|
||||
{
|
||||
Env env(*this);
|
||||
auto T = [&env](NetClock::duration const& d)
|
||||
{ return env.clock.now() + d; };
|
||||
{ return env.now() + d; };
|
||||
env.fund(XRP(5000), "alice", "bob", "carol");
|
||||
auto const c = cond("receipt");
|
||||
auto const seq = env.seq("alice");
|
||||
@@ -320,7 +320,7 @@ struct SusPay_test : public beast::unit_test::suite
|
||||
{
|
||||
Env env(*this);
|
||||
auto T = [&env](NetClock::duration const& d)
|
||||
{ return env.clock.now() + d; };
|
||||
{ return env.now() + d; };
|
||||
env.fund(XRP(5000), "alice", "bob", "carol");
|
||||
auto const p = from_hex_text<uint128>(
|
||||
"0102030405060708090A0B0C0D0E0F");
|
||||
@@ -340,7 +340,7 @@ struct SusPay_test : public beast::unit_test::suite
|
||||
using S = seconds;
|
||||
Env env(*this);
|
||||
auto T = [&env](NetClock::duration const& d)
|
||||
{ return env.clock.now() + d; };
|
||||
{ return env.now() + d; };
|
||||
env.fund(XRP(5000), "alice", "bob", "carol");
|
||||
auto const c = cond("receipt");
|
||||
env(condpay("alice", "carol", XRP(1000), c.first, T(S{1})));
|
||||
|
||||
@@ -42,7 +42,7 @@ class TxQ_test : public TestSuite
|
||||
std::uint64_t expectedMinFeeLevel,
|
||||
std::uint64_t expectedMedFeeLevel)
|
||||
{
|
||||
auto metrics = env.app().getTxQ().getMetrics(*env.open());
|
||||
auto metrics = env.app().getTxQ().getMetrics(*env.current());
|
||||
expect(metrics.referenceFeeLevel == 256, "referenceFeeLevel");
|
||||
expect(metrics.txCount == expectedCount, "txCount");
|
||||
expect(metrics.txQMaxSize == expectedMaxCount, "txQMaxSize");
|
||||
@@ -62,24 +62,10 @@ class TxQ_test : public TestSuite
|
||||
close(jtx::Env& env, size_t expectedTxSetSize, bool timeLeap = false)
|
||||
{
|
||||
{
|
||||
auto const view = env.open();
|
||||
auto const view = env.current();
|
||||
expect(view->txCount() == expectedTxSetSize, "TxSet size mismatch");
|
||||
// Update fee computations.
|
||||
// Note implementing this way assumes that everything
|
||||
// in the open ledger _will_ make it into the closed
|
||||
// ledger, but for metrics that's probably good enough.
|
||||
env.app().getTxQ().processValidatedLedger(
|
||||
env.app(), *view, timeLeap, tapENABLE_TESTING);
|
||||
}
|
||||
|
||||
env.close(
|
||||
[&](OpenView& view, beast::Journal j)
|
||||
{
|
||||
// Stuff the ledger with transactions from the queue.
|
||||
return env.app().getTxQ().accept(env.app(), view,
|
||||
tapENABLE_TESTING);
|
||||
}
|
||||
);
|
||||
env.close();
|
||||
}
|
||||
|
||||
void
|
||||
@@ -92,7 +78,7 @@ class TxQ_test : public TestSuite
|
||||
bool didApply;
|
||||
TER ter;
|
||||
|
||||
env.openLedger.modify(
|
||||
env.app().openLedger().modify(
|
||||
[&](OpenView& view, beast::Journal j)
|
||||
{
|
||||
std::tie(ter, didApply) =
|
||||
@@ -108,7 +94,7 @@ class TxQ_test : public TestSuite
|
||||
}
|
||||
|
||||
static
|
||||
std::unique_ptr<Config const>
|
||||
std::unique_ptr<Config>
|
||||
makeConfig()
|
||||
{
|
||||
auto p = std::make_unique<Config>();
|
||||
@@ -118,7 +104,7 @@ class TxQ_test : public TestSuite
|
||||
section.set("min_ledgers_to_compute_size_limit", "3");
|
||||
section.set("max_ledger_counts_to_store", "100");
|
||||
section.set("retry_sequence_percent", "125");
|
||||
return std::move(p);
|
||||
return p;
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -142,7 +128,7 @@ public:
|
||||
|
||||
auto queued = ter(terQUEUED);
|
||||
|
||||
expectEquals(env.open()->fees().base, 10);
|
||||
expectEquals(env.current()->fees().base, 10);
|
||||
|
||||
checkMetrics(env, 0, boost::none, 0, 3, 256, 500);
|
||||
|
||||
@@ -164,7 +150,7 @@ public:
|
||||
auto openLedgerFee =
|
||||
[&]()
|
||||
{
|
||||
return fee(txq.openLedgerFee(*env.open()));
|
||||
return fee(txq.openLedgerFee(*env.current()));
|
||||
};
|
||||
// Alice's next transaction -
|
||||
// fails because the item in the TxQ hasn't applied.
|
||||
@@ -339,7 +325,7 @@ public:
|
||||
// we can be sure that there's one in the queue when the
|
||||
// test ends and the TxQ is destructed.
|
||||
|
||||
auto metrics = txq.getMetrics(*env.open());
|
||||
auto metrics = txq.getMetrics(*env.current());
|
||||
expect(metrics.txCount == 0, "txCount");
|
||||
auto txnsNeeded = metrics.txPerLedger - metrics.txInLedger;
|
||||
|
||||
|
||||
@@ -168,8 +168,9 @@ SusPayCreate::doApply()
|
||||
weeks{1}).time_since_epoch().count();
|
||||
if (ctx_.tx[~sfDigest])
|
||||
{
|
||||
if (! ctx_.tx[~sfCancelAfter] ||
|
||||
maxExpire <= ctx_.tx[sfCancelAfter])
|
||||
if (! ctx_.tx[~sfCancelAfter])
|
||||
return tecNO_PERMISSION;
|
||||
if (maxExpire <= ctx_.tx[sfCancelAfter])
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -55,10 +55,6 @@ public:
|
||||
static bool const is_steady = false;
|
||||
};
|
||||
|
||||
/** A manual NetClock for unit tests. */
|
||||
using TestNetClock =
|
||||
beast::manual_clock<NetClock>;
|
||||
|
||||
/** A clock for measuring elapsed time.
|
||||
|
||||
The epoch is unspecified.
|
||||
|
||||
@@ -514,8 +514,11 @@ initAuthenticated (boost::asio::ssl::context& context,
|
||||
std::shared_ptr<boost::asio::ssl::context>
|
||||
make_SSLContext()
|
||||
{
|
||||
std::shared_ptr<boost::asio::ssl::context> context =
|
||||
std::make_shared<boost::asio::ssl::context> (
|
||||
static auto const context =
|
||||
[]()
|
||||
{
|
||||
auto const context = std::make_shared<
|
||||
boost::asio::ssl::context>(
|
||||
boost::asio::ssl::context::sslv23);
|
||||
// By default, allow anonymous DH.
|
||||
openssl::detail::initAnonymous(
|
||||
@@ -524,6 +527,8 @@ make_SSLContext()
|
||||
// set_verify_mode called, for either setting of WEBSOCKET_SECURE
|
||||
context->set_verify_mode(boost::asio::ssl::verify_none);
|
||||
return context;
|
||||
}();
|
||||
return context;
|
||||
}
|
||||
|
||||
std::shared_ptr<boost::asio::ssl::context>
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#define RIPPLE_CORE_TIMEKEEPER_H_INCLUDED
|
||||
|
||||
#include <beast/chrono/abstract_clock.h>
|
||||
#include <beast/utility/Journal.h>
|
||||
#include <ripple/basics/chrono.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -90,7 +91,6 @@ public:
|
||||
virtual
|
||||
std::chrono::duration<std::int32_t>
|
||||
closeOffset() const = 0;
|
||||
|
||||
};
|
||||
|
||||
extern
|
||||
|
||||
@@ -35,26 +35,26 @@ struct BookDirs_test : public beast::unit_test::suite
|
||||
{
|
||||
Book book(xrpIssue(), USD.issue());
|
||||
{
|
||||
auto d = BookDirs(*env.open(), book);
|
||||
auto d = BookDirs(*env.current(), book);
|
||||
expect(std::begin(d) == std::end(d));
|
||||
expect(std::distance(d.begin(), d.end()) == 0);
|
||||
}
|
||||
{
|
||||
auto d = BookDirs(*env.open(), reversed(book));
|
||||
auto d = BookDirs(*env.current(), reversed(book));
|
||||
expect(std::distance(d.begin(), d.end()) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
env(offer("alice", Account("alice")["USD"](50), XRP(10)));
|
||||
auto d = BookDirs(*env.open(),
|
||||
auto d = BookDirs(*env.current(),
|
||||
Book(Account("alice")["USD"].issue(), xrpIssue()));
|
||||
expect(std::distance(d.begin(), d.end()) == 1);
|
||||
}
|
||||
|
||||
{
|
||||
env(offer("alice", gw["CNY"](50), XRP(10)));
|
||||
auto d = BookDirs(*env.open(),
|
||||
auto d = BookDirs(*env.current(),
|
||||
Book(gw["CNY"].issue(), xrpIssue()));
|
||||
expect(std::distance(d.begin(), d.end()) == 1);
|
||||
}
|
||||
@@ -63,7 +63,7 @@ struct BookDirs_test : public beast::unit_test::suite
|
||||
env.trust(Account("bob")["CNY"](10), "alice");
|
||||
env(pay("bob", "alice", Account("bob")["CNY"](10)));
|
||||
env(offer("alice", USD(50), Account("bob")["CNY"](10)));
|
||||
auto d = BookDirs(*env.open(),
|
||||
auto d = BookDirs(*env.current(),
|
||||
Book(USD.issue(), Account("bob")["CNY"].issue()));
|
||||
expect(std::distance(d.begin(), d.end()) == 1);
|
||||
}
|
||||
@@ -74,7 +74,7 @@ struct BookDirs_test : public beast::unit_test::suite
|
||||
for (auto k = 0; k < 80; ++k)
|
||||
env(offer("alice", AUD(i), XRP(j)));
|
||||
|
||||
auto d = BookDirs(*env.open(),
|
||||
auto d = BookDirs(*env.current(),
|
||||
Book(AUD.issue(), xrpIssue()));
|
||||
expect(std::distance(d.begin(), d.end()) == 240);
|
||||
auto i = 1, j = 3, k = 0;
|
||||
|
||||
@@ -32,7 +32,7 @@ struct Directory_test : public beast::unit_test::suite
|
||||
auto USD = gw["USD"];
|
||||
|
||||
{
|
||||
auto dir = Dir(*env.open(),
|
||||
auto dir = Dir(*env.current(),
|
||||
keylet::ownerDir(Account("alice")));
|
||||
expect(std::begin(dir) == std::end(dir));
|
||||
expect(std::end(dir) == dir.find(uint256(), uint256()));
|
||||
@@ -46,13 +46,13 @@ struct Directory_test : public beast::unit_test::suite
|
||||
env(offer("bob", USD(500), XRP(10)));
|
||||
|
||||
{
|
||||
auto dir = Dir(*env.open(),
|
||||
auto dir = Dir(*env.current(),
|
||||
keylet::ownerDir(Account("bob")));
|
||||
expect(std::begin(dir)->get()->
|
||||
getFieldAmount(sfTakerPays) == USD(500));
|
||||
}
|
||||
|
||||
auto dir = Dir(*env.open(),
|
||||
auto dir = Dir(*env.current(),
|
||||
keylet::ownerDir(Account("alice")));
|
||||
i = 0;
|
||||
for (auto const& e : dir)
|
||||
|
||||
@@ -34,7 +34,7 @@ isOffer (jtx::Env& env,
|
||||
STAmount const& takerGets)
|
||||
{
|
||||
bool exists = false;
|
||||
forEachItem (*env.open(), account,
|
||||
forEachItem (*env.current(), account,
|
||||
[&](std::shared_ptr<SLE const> const& sle)
|
||||
{
|
||||
if (sle->getType () == ltOFFER &&
|
||||
|
||||
@@ -120,7 +120,7 @@ class PaymentSandbox_test : public beast::unit_test::suite
|
||||
STAmount const toDebit (USD_gw1 (20));
|
||||
{
|
||||
// accountSend, no deferredCredits
|
||||
ApplyViewImpl av (&*env.open(), tapNONE);
|
||||
ApplyViewImpl av (&*env.current(), tapNONE);
|
||||
|
||||
auto const iss = USD_gw1.issue ();
|
||||
auto const startingAmount = accountHolds (
|
||||
@@ -139,7 +139,7 @@ class PaymentSandbox_test : public beast::unit_test::suite
|
||||
|
||||
{
|
||||
// rippleCredit, no deferredCredits
|
||||
ApplyViewImpl av (&*env.open(), tapNONE);
|
||||
ApplyViewImpl av (&*env.current(), tapNONE);
|
||||
|
||||
auto const iss = USD_gw1.issue ();
|
||||
auto const startingAmount = accountHolds (
|
||||
@@ -158,7 +158,7 @@ class PaymentSandbox_test : public beast::unit_test::suite
|
||||
|
||||
{
|
||||
// accountSend, w/ deferredCredits
|
||||
ApplyViewImpl av (&*env.open(), tapNONE);
|
||||
ApplyViewImpl av (&*env.current(), tapNONE);
|
||||
PaymentSandbox pv (&av);
|
||||
|
||||
auto const iss = USD_gw1.issue ();
|
||||
@@ -178,7 +178,7 @@ class PaymentSandbox_test : public beast::unit_test::suite
|
||||
|
||||
{
|
||||
// rippleCredit, w/ deferredCredits
|
||||
ApplyViewImpl av (&*env.open(), tapNONE);
|
||||
ApplyViewImpl av (&*env.current(), tapNONE);
|
||||
PaymentSandbox pv (&av);
|
||||
|
||||
auto const iss = USD_gw1.issue ();
|
||||
@@ -193,7 +193,7 @@ class PaymentSandbox_test : public beast::unit_test::suite
|
||||
|
||||
{
|
||||
// redeemIOU, w/ deferredCredits
|
||||
ApplyViewImpl av (&*env.open(), tapNONE);
|
||||
ApplyViewImpl av (&*env.current(), tapNONE);
|
||||
PaymentSandbox pv (&av);
|
||||
|
||||
auto const iss = USD_gw1.issue ();
|
||||
@@ -208,7 +208,7 @@ class PaymentSandbox_test : public beast::unit_test::suite
|
||||
|
||||
{
|
||||
// issueIOU, w/ deferredCredits
|
||||
ApplyViewImpl av (&*env.open(), tapNONE);
|
||||
ApplyViewImpl av (&*env.current(), tapNONE);
|
||||
PaymentSandbox pv (&av);
|
||||
|
||||
auto const iss = USD_gw1.issue ();
|
||||
@@ -223,7 +223,7 @@ class PaymentSandbox_test : public beast::unit_test::suite
|
||||
|
||||
{
|
||||
// accountSend, w/ deferredCredits and stacked views
|
||||
ApplyViewImpl av (&*env.open(), tapNONE);
|
||||
ApplyViewImpl av (&*env.current(), tapNONE);
|
||||
PaymentSandbox pv (&av);
|
||||
|
||||
auto const iss = USD_gw1.issue ();
|
||||
|
||||
@@ -183,8 +183,8 @@ class View_test
|
||||
{
|
||||
using namespace jtx;
|
||||
Env env(*this);
|
||||
wipe(env.openLedger);
|
||||
auto const open = env.open();
|
||||
wipe(env.app().openLedger());
|
||||
auto const open = env.current();
|
||||
ApplyViewImpl v(&*open, tapNONE);
|
||||
succ(v, 0, boost::none);
|
||||
v.insert(sle(1));
|
||||
@@ -214,8 +214,8 @@ class View_test
|
||||
{
|
||||
using namespace jtx;
|
||||
Env env(*this);
|
||||
wipe(env.openLedger);
|
||||
auto const open = env.open();
|
||||
wipe(env.app().openLedger());
|
||||
auto const open = env.current();
|
||||
ApplyViewImpl v0(&*open, tapNONE);
|
||||
v0.insert(sle(1));
|
||||
v0.insert(sle(2));
|
||||
@@ -278,8 +278,8 @@ class View_test
|
||||
{
|
||||
using namespace jtx;
|
||||
Env env(*this);
|
||||
wipe(env.openLedger);
|
||||
auto const open = env.open();
|
||||
wipe(env.app().openLedger());
|
||||
auto const open = env.current();
|
||||
ApplyViewImpl v0 (&*open, tapNONE);
|
||||
v0.rawInsert(sle(1, 1));
|
||||
v0.rawInsert(sle(2, 2));
|
||||
@@ -345,8 +345,8 @@ class View_test
|
||||
using namespace std::chrono;
|
||||
{
|
||||
Env env(*this);
|
||||
wipe(env.openLedger);
|
||||
auto const open = env.open();
|
||||
wipe(env.app().openLedger());
|
||||
auto const open = env.current();
|
||||
OpenView v0(open.get());
|
||||
expect(v0.seq() != 98);
|
||||
expect(v0.seq() == open->seq());
|
||||
@@ -556,9 +556,9 @@ class View_test
|
||||
// Make sure OpenLedger::empty works
|
||||
{
|
||||
Env env(*this);
|
||||
expect(env.openLedger.empty());
|
||||
expect(env.app().openLedger().empty());
|
||||
env.fund(XRP(10000), Account("test"));
|
||||
expect(!env.openLedger.empty());
|
||||
expect(! env.app().openLedger().empty());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1658,7 +1658,7 @@ public:
|
||||
env(pay(g, env.master, USD(50)));
|
||||
env.close();
|
||||
|
||||
auto const ledger = env.open();
|
||||
auto const ledger = env.current();
|
||||
|
||||
ProcessTransactionFn processTxn = fakeProcessTransaction;
|
||||
|
||||
|
||||
69
src/ripple/test/ManualTimeKeeper.h
Normal file
69
src/ripple/test/ManualTimeKeeper.h
Normal file
@@ -0,0 +1,69 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012, 2013 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.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_TEST_MANUALTIMEKEEPER_H_INCLUDED
|
||||
#define RIPPLE_TEST_MANUALTIMEKEEPER_H_INCLUDED
|
||||
|
||||
#include <ripple/core/TimeKeeper.h>
|
||||
#include <mutex>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
class ManualTimeKeeper : public TimeKeeper
|
||||
{
|
||||
public:
|
||||
ManualTimeKeeper();
|
||||
|
||||
void
|
||||
run (std::vector<std::string> const& servers) override;
|
||||
|
||||
time_point
|
||||
now() const override;
|
||||
|
||||
time_point
|
||||
closeTime() const override;
|
||||
|
||||
void
|
||||
adjustCloseTime (std::chrono::duration<std::int32_t> amount) override;
|
||||
|
||||
std::chrono::duration<std::int32_t>
|
||||
nowOffset() const override;
|
||||
|
||||
std::chrono::duration<std::int32_t>
|
||||
closeOffset() const override;
|
||||
|
||||
void
|
||||
set (time_point now);
|
||||
|
||||
private:
|
||||
// Adjust system_clock::time_point for NetClock epoch
|
||||
static
|
||||
time_point
|
||||
adjust (std::chrono::system_clock::time_point when);
|
||||
|
||||
std::mutex mutable mutex_;
|
||||
std::chrono::duration<std::int32_t> closeOffset_;
|
||||
time_point now_;
|
||||
};
|
||||
|
||||
} // test
|
||||
} // ripple
|
||||
|
||||
#endif
|
||||
104
src/ripple/test/impl/ManualTimeKeeper.cpp
Normal file
104
src/ripple/test/impl/ManualTimeKeeper.cpp
Normal file
@@ -0,0 +1,104 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012-2015 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 <BeastConfig.h>
|
||||
#include <ripple/test/ManualTimeKeeper.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
ManualTimeKeeper::ManualTimeKeeper()
|
||||
: closeOffset_ {}
|
||||
, now_ (0s)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
ManualTimeKeeper::run (std::vector<std::string> const& servers)
|
||||
{
|
||||
}
|
||||
|
||||
auto
|
||||
ManualTimeKeeper::now() const ->
|
||||
time_point
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return now_;
|
||||
}
|
||||
|
||||
auto
|
||||
ManualTimeKeeper::closeTime() const ->
|
||||
time_point
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return now_ + closeOffset_;
|
||||
}
|
||||
|
||||
void
|
||||
ManualTimeKeeper::adjustCloseTime(
|
||||
std::chrono::duration<std::int32_t> amount)
|
||||
{
|
||||
// Copied from TimeKeeper::adjustCloseTime
|
||||
using namespace std::chrono;
|
||||
auto const s = amount.count();
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
// Take large offsets, ignore small offsets,
|
||||
// push the close time towards our wall time.
|
||||
if (s > 1)
|
||||
closeOffset_ += seconds((s + 3) / 4);
|
||||
else if (s < -1)
|
||||
closeOffset_ += seconds((s - 3) / 4);
|
||||
else
|
||||
closeOffset_ = (closeOffset_ * 3) / 4;
|
||||
}
|
||||
|
||||
std::chrono::duration<std::int32_t>
|
||||
ManualTimeKeeper::nowOffset() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
std::chrono::duration<std::int32_t>
|
||||
ManualTimeKeeper::closeOffset() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return closeOffset_;
|
||||
}
|
||||
|
||||
void
|
||||
ManualTimeKeeper::set (time_point now)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
now_ = now;
|
||||
}
|
||||
|
||||
auto
|
||||
ManualTimeKeeper::adjust(
|
||||
std::chrono::system_clock::time_point when) ->
|
||||
time_point
|
||||
{
|
||||
return time_point(
|
||||
std::chrono::duration_cast<duration>(
|
||||
when.time_since_epoch() -
|
||||
days(10957)));
|
||||
}
|
||||
} // test
|
||||
} // ripple
|
||||
@@ -23,6 +23,8 @@
|
||||
#include <ripple/protocol/SecretKey.h>
|
||||
#include <ripple/protocol/UintTypes.h>
|
||||
#include <ripple/crypto/KeyType.h>
|
||||
#include <beast/hash/uhash.h>
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
|
||||
namespace ripple {
|
||||
@@ -35,23 +37,18 @@ class IOU;
|
||||
class Account
|
||||
{
|
||||
private:
|
||||
std::string name_;
|
||||
PublicKey pk_;
|
||||
SecretKey sk_;
|
||||
AccountID id_;
|
||||
std::string human_; // base58 public key string
|
||||
|
||||
// Tag for access to private contr
|
||||
struct privateCtorTag{};
|
||||
public:
|
||||
/** The master account. */
|
||||
static Account const master;
|
||||
|
||||
Account() = default;
|
||||
Account (Account&&) = default;
|
||||
Account (Account const&) = default;
|
||||
Account& operator= (Account const&) = default;
|
||||
Account (Account&&) = default;
|
||||
Account& operator= (Account&&) = default;
|
||||
|
||||
/** Create an account from a key pair. */
|
||||
Account (std::string name,
|
||||
std::pair<PublicKey, SecretKey> const& keys);
|
||||
|
||||
/** Create an account from a simple string name. */
|
||||
/** @{ */
|
||||
Account (std::string name,
|
||||
@@ -62,6 +59,13 @@ public:
|
||||
: Account(std::string(name), type)
|
||||
{
|
||||
}
|
||||
|
||||
// This constructor needs to be public so `std::pair` can use it when emplacing
|
||||
// into the cache. However, it is logically `private`. This is enforced with the
|
||||
// `privateTag` parameter.
|
||||
Account (std::string name,
|
||||
std::pair<PublicKey, SecretKey> const& keys, Account::privateCtorTag);
|
||||
|
||||
/** @} */
|
||||
|
||||
/** Return the name */
|
||||
@@ -115,6 +119,21 @@ public:
|
||||
/** Returns an IOU for the specified gateway currency. */
|
||||
IOU
|
||||
operator[](std::string const& s) const;
|
||||
|
||||
private:
|
||||
static std::unordered_map<
|
||||
std::pair<std::string, KeyType>,
|
||||
Account, beast::uhash<>> cache_;
|
||||
|
||||
|
||||
// Return the account from the cache & add it to the cache if needed
|
||||
static Account fromCache(std::string name, KeyType type);
|
||||
|
||||
std::string name_;
|
||||
PublicKey pk_;
|
||||
SecretKey sk_;
|
||||
AccountID id_;
|
||||
std::string human_; // base58 public key string
|
||||
};
|
||||
|
||||
inline
|
||||
|
||||
@@ -25,9 +25,11 @@
|
||||
#include <ripple/test/jtx/JTx.h>
|
||||
#include <ripple/test/jtx/require.h>
|
||||
#include <ripple/test/jtx/tags.h>
|
||||
#include <ripple/test/ManualTimeKeeper.h>
|
||||
#include <ripple/app/main/Application.h>
|
||||
#include <ripple/app/ledger/Ledger.h>
|
||||
#include <ripple/app/ledger/OpenLedger.h>
|
||||
#include <ripple/app/paths/Pathfinder.h>
|
||||
#include <ripple/basics/chrono.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/core/Config.h>
|
||||
@@ -49,67 +51,29 @@
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
extern
|
||||
void
|
||||
setupConfigForUnitTests (Config& config);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace jtx {
|
||||
|
||||
namespace detail {
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
||||
// Workaround for C2797:
|
||||
// list initialization inside member initializer
|
||||
// list or non-static data member initializer is
|
||||
// not implemented
|
||||
//
|
||||
template <std::size_t N>
|
||||
struct noripple_helper
|
||||
{
|
||||
std::array<Account, N> args;
|
||||
|
||||
template <class T, class ...Args>
|
||||
std::array<Account, sizeof...(Args)>
|
||||
flatten (Args&& ...args)
|
||||
{
|
||||
return std::array<T,
|
||||
sizeof...(Args)> {
|
||||
std::forward<Args>(args)...};
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
explicit
|
||||
noripple_helper(Args const&... args_)
|
||||
: args(flatten<Account>(args_...))
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
template <std::size_t N>
|
||||
struct noripple_helper
|
||||
{
|
||||
std::array<Account, N> args;
|
||||
|
||||
template <class... Args>
|
||||
explicit
|
||||
noripple_helper(Args const&... args_)
|
||||
: args { { args_... } }
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
} // detail
|
||||
|
||||
/** Designate accounts as no-ripple in Env::fund */
|
||||
template <class... Args>
|
||||
detail::noripple_helper<1 + sizeof...(Args)>
|
||||
noripple (Account const& account,
|
||||
Args const&... args)
|
||||
std::array<Account, 1 + sizeof...(Args)>
|
||||
noripple (Account const& account, Args const&... args)
|
||||
{
|
||||
return detail::noripple_helper<
|
||||
1 + sizeof...(Args)>(
|
||||
account, args...);
|
||||
return {{account, args...}};
|
||||
}
|
||||
|
||||
/** Activate features in the Env ctor */
|
||||
template <class... Args>
|
||||
std::array<uint256, 1 + sizeof...(Args)>
|
||||
features (uint256 const& key, Args const&... args)
|
||||
{
|
||||
return {{key, args...}};
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -118,16 +82,11 @@ noripple (Account const& account,
|
||||
class Env
|
||||
{
|
||||
public:
|
||||
using clock_type = TestNetClock;
|
||||
|
||||
clock_type clock;
|
||||
|
||||
beast::unit_test::suite& test;
|
||||
|
||||
beast::Journal const journal;
|
||||
|
||||
/** The master account. */
|
||||
Account const master;
|
||||
Account const& master = Account::master;
|
||||
|
||||
private:
|
||||
struct AppBundle
|
||||
@@ -135,27 +94,70 @@ private:
|
||||
Application* app;
|
||||
std::unique_ptr<Logs> logs;
|
||||
std::unique_ptr<Application> owned;
|
||||
ManualTimeKeeper* timeKeeper;
|
||||
std::thread thread;
|
||||
|
||||
AppBundle (std::unique_ptr<Config const> config);
|
||||
AppBundle (std::unique_ptr<Config> config);
|
||||
AppBundle (Application* app_);
|
||||
~AppBundle();
|
||||
};
|
||||
|
||||
AppBundle bundle_;
|
||||
std::shared_ptr<Ledger const> closed_;
|
||||
CachedSLEs cachedSLEs_;
|
||||
LogSquelcher logSquelcher_;
|
||||
|
||||
public:
|
||||
// Careful with this
|
||||
OpenLedger openLedger;
|
||||
inline
|
||||
void
|
||||
construct()
|
||||
{
|
||||
}
|
||||
|
||||
template <class Arg, class... Args>
|
||||
void
|
||||
construct (Arg&& arg, Args&&... args)
|
||||
{
|
||||
construct_arg(std::forward<Arg>(arg));
|
||||
construct(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<std::size_t N>
|
||||
void
|
||||
construct_arg (
|
||||
std::array<uint256, N> const& list)
|
||||
{
|
||||
for(auto const& key : list)
|
||||
app().config().features.insert(key);
|
||||
}
|
||||
|
||||
public:
|
||||
Env() = delete;
|
||||
Env (Env const&) = delete;
|
||||
Env& operator= (Env const&) = delete;
|
||||
|
||||
// VFALCO Could wrap the suite::log in a Journal here
|
||||
template <class... Args>
|
||||
Env (beast::unit_test::suite& test_,
|
||||
std::unique_ptr<Config const> config);
|
||||
Env (beast::unit_test::suite& test_);
|
||||
std::unique_ptr<Config> config,
|
||||
Args&&... args)
|
||||
: test (test_)
|
||||
, bundle_ (std::move(config))
|
||||
{
|
||||
memoize(Account::master);
|
||||
Pathfinder::initPathTable();
|
||||
construct(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
Env (beast::unit_test::suite& test_,
|
||||
Args&&... args)
|
||||
: Env(test_, []()
|
||||
{
|
||||
auto p = std::make_unique<Config>();
|
||||
setupConfigForUnitTests(*p);
|
||||
return p;
|
||||
}(), std::forward<Args>(args)...)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
Application&
|
||||
app()
|
||||
@@ -163,7 +165,24 @@ public:
|
||||
return *bundle_.app;
|
||||
}
|
||||
|
||||
/** Returns the open ledger.
|
||||
Application const&
|
||||
app() const
|
||||
{
|
||||
return *bundle_.app;
|
||||
}
|
||||
|
||||
/** Returns the current Ripple Network Time
|
||||
|
||||
@note This is manually advanced when ledgers
|
||||
close or by callers.
|
||||
*/
|
||||
NetClock::time_point
|
||||
now()
|
||||
{
|
||||
return app().timeKeeper().now();
|
||||
}
|
||||
|
||||
/** Returns the current ledger.
|
||||
|
||||
This is a non-modifiable snapshot of the
|
||||
open ledger at the moment of the call.
|
||||
@@ -172,7 +191,10 @@ public:
|
||||
|
||||
*/
|
||||
std::shared_ptr<OpenView const>
|
||||
open() const;
|
||||
current() const
|
||||
{
|
||||
return app().openLedger().current();
|
||||
}
|
||||
|
||||
/** Returns the last closed ledger.
|
||||
|
||||
@@ -182,10 +204,14 @@ public:
|
||||
and a new open ledger takes its place.
|
||||
*/
|
||||
std::shared_ptr<ReadView const>
|
||||
closed() const;
|
||||
closed();
|
||||
|
||||
/** Close and advance the ledger.
|
||||
|
||||
The resulting close time will be different and
|
||||
greater than the previous close time, and at or
|
||||
after the passed-in close time.
|
||||
|
||||
Effects:
|
||||
|
||||
Creates a new closed ledger from the last
|
||||
@@ -194,11 +220,11 @@ public:
|
||||
All transactions that made it into the open
|
||||
ledger are applied to the closed ledger.
|
||||
|
||||
The Env clock is set to the new time.
|
||||
The Application network time is set to
|
||||
the close time of the resulting ledger.
|
||||
*/
|
||||
void
|
||||
close (NetClock::time_point const& closeTime,
|
||||
OpenLedger::modify_type const& f = {});
|
||||
close (NetClock::time_point closeTime);
|
||||
|
||||
/** Close and advance the ledger.
|
||||
|
||||
@@ -208,11 +234,10 @@ public:
|
||||
template <class Rep, class Period>
|
||||
void
|
||||
close (std::chrono::duration<
|
||||
Rep, Period> const& elapsed,
|
||||
OpenLedger::modify_type const& f = {})
|
||||
Rep, Period> const& elapsed)
|
||||
{
|
||||
stopwatch_.advance(elapsed);
|
||||
close (clock.now() + elapsed, f);
|
||||
// VFALCO Is this the correct time?
|
||||
close (now() + elapsed);
|
||||
}
|
||||
|
||||
/** Close and advance the ledger.
|
||||
@@ -221,9 +246,10 @@ public:
|
||||
the previous ledger closing time.
|
||||
*/
|
||||
void
|
||||
close(OpenLedger::modify_type const& f = {})
|
||||
close()
|
||||
{
|
||||
close (std::chrono::seconds(5), f);
|
||||
// VFALCO Is this the correct time?
|
||||
close (std::chrono::seconds(5));
|
||||
}
|
||||
|
||||
/** Turn on JSON tracing.
|
||||
@@ -417,9 +443,9 @@ private:
|
||||
template <std::size_t N>
|
||||
void
|
||||
fund_arg (STAmount const& amount,
|
||||
detail::noripple_helper<N> const& list)
|
||||
std::array<Account, N> const& list)
|
||||
{
|
||||
for (auto const& account : list.args)
|
||||
for (auto const& account : list)
|
||||
fund (false, amount, account);
|
||||
}
|
||||
public:
|
||||
|
||||
@@ -26,8 +26,16 @@ namespace ripple {
|
||||
namespace test {
|
||||
namespace jtx {
|
||||
|
||||
std::unordered_map<
|
||||
std::pair<std::string, KeyType>,
|
||||
Account, beast::uhash<>> Account::cache_;
|
||||
|
||||
Account const Account::master("master",
|
||||
generateKeyPair(KeyType::secp256k1,
|
||||
generateSeed("masterpassphrase")), Account::privateCtorTag{});
|
||||
|
||||
Account::Account(std::string name,
|
||||
std::pair<PublicKey, SecretKey> const& keys)
|
||||
std::pair<PublicKey, SecretKey> const& keys, Account::privateCtorTag)
|
||||
: name_(std::move(name))
|
||||
, pk_ (keys.first)
|
||||
, sk_ (keys.second)
|
||||
@@ -36,15 +44,23 @@ Account::Account(std::string name,
|
||||
{
|
||||
}
|
||||
|
||||
Account::Account (std::string name, KeyType type)
|
||||
: name_(std::move(name))
|
||||
Account Account::fromCache(std::string name, KeyType type)
|
||||
{
|
||||
auto const p = std::make_pair (name, type);
|
||||
auto const iter = cache_.find (p);
|
||||
if (iter != cache_.end ())
|
||||
return iter->second;
|
||||
|
||||
auto const keys = generateKeyPair (type, generateSeed (name));
|
||||
auto r = cache_.emplace (std::piecewise_construct,
|
||||
std::forward_as_tuple (std::move (p)),
|
||||
std::forward_as_tuple (std::move (name), keys, privateCtorTag{}));
|
||||
return r.first->second;
|
||||
}
|
||||
|
||||
Account::Account (std::string name, KeyType type)
|
||||
: Account (fromCache (std::move (name), type))
|
||||
{
|
||||
auto const keys = generateKeyPair(
|
||||
type, generateSeed(name_));
|
||||
pk_ = keys.first;
|
||||
sk_ = keys.second;
|
||||
id_ = calcAccountID(pk_);
|
||||
human_ = toBase58(id_);
|
||||
}
|
||||
|
||||
IOU
|
||||
|
||||
@@ -29,10 +29,12 @@
|
||||
#include <ripple/test/jtx/sig.h>
|
||||
#include <ripple/test/jtx/utility.h>
|
||||
#include <ripple/app/tx/apply.h>
|
||||
#include <ripple/app/ledger/LedgerMaster.h>
|
||||
#include <ripple/app/ledger/LedgerTiming.h>
|
||||
#include <ripple/app/paths/Pathfinder.h>
|
||||
#include <ripple/app/misc/NetworkOPs.h>
|
||||
#include <ripple/basics/contract.h>
|
||||
#include <ripple/basics/Slice.h>
|
||||
#include <ripple/core/ConfigSections.h>
|
||||
#include <ripple/json/to_string.h>
|
||||
#include <ripple/protocol/ErrorCodes.h>
|
||||
#include <ripple/protocol/HashPrefix.h>
|
||||
@@ -50,6 +52,29 @@
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
void
|
||||
setupConfigForUnitTests (Config& config)
|
||||
{
|
||||
config.overwrite (ConfigSection::nodeDatabase (), "type", "memory");
|
||||
config.overwrite (ConfigSection::nodeDatabase (), "path", "main");
|
||||
|
||||
config.deprecatedClearSection (ConfigSection::importNodeDatabase ());
|
||||
config.legacy("database_path", "DummyForUnitTests");
|
||||
|
||||
config.RUN_STANDALONE = true;
|
||||
config["server"].append("port_peer");
|
||||
config["port_peer"].set("ip", "127.0.0.1");
|
||||
config["port_peer"].set("port", "8080");
|
||||
config["port_peer"].set("protocol", "peer");
|
||||
config["server"].append("port_admin");
|
||||
config["port_admin"].set("ip", "127.0.0.1");
|
||||
config["port_admin"].set("port", "8081");
|
||||
config["port_admin"].set("protocol", "http");
|
||||
config["port_admin"].set("admin", "127.0.0.1");
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace jtx {
|
||||
|
||||
Env::AppBundle::AppBundle(Application* app_)
|
||||
@@ -57,90 +82,45 @@ Env::AppBundle::AppBundle(Application* app_)
|
||||
{
|
||||
}
|
||||
|
||||
Env::AppBundle::AppBundle(std::unique_ptr<Config const> config)
|
||||
Env::AppBundle::AppBundle(std::unique_ptr<Config> config)
|
||||
{
|
||||
auto logs = std::make_unique<Logs>();
|
||||
owned = make_Application(
|
||||
std::move(config), std::move(logs));
|
||||
auto timeKeeper_ =
|
||||
std::make_unique<ManualTimeKeeper>();
|
||||
timeKeeper = timeKeeper_.get();
|
||||
owned = make_Application(std::move(config),
|
||||
std::move(logs), std::move(timeKeeper_));
|
||||
app = owned.get();
|
||||
app->setup();
|
||||
timeKeeper->set(
|
||||
app->getLedgerMaster().getClosedLedger()->info().closeTime);
|
||||
thread = std::thread(
|
||||
[&](){ app->run(); });
|
||||
}
|
||||
|
||||
Env::AppBundle::~AppBundle()
|
||||
{
|
||||
app->signalStop();
|
||||
thread.join();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
static
|
||||
std::unique_ptr<Config const>
|
||||
makeConfig()
|
||||
{
|
||||
auto p = std::make_unique<Config>();
|
||||
setupConfigForUnitTests(*p);
|
||||
return std::unique_ptr<Config const>(p.release());
|
||||
}
|
||||
|
||||
// VFALCO Could wrap the log in a Journal here
|
||||
Env::Env(beast::unit_test::suite& test_,
|
||||
std::unique_ptr<Config const> config)
|
||||
: test (test_)
|
||||
, master ("master", generateKeyPair(
|
||||
KeyType::secp256k1,
|
||||
generateSeed("masterpassphrase")))
|
||||
, bundle_ (std::move(config))
|
||||
, closed_ (std::make_shared<Ledger>(
|
||||
create_genesis, app().config(), app().family()))
|
||||
, cachedSLEs_ (std::chrono::seconds(5), stopwatch_)
|
||||
, openLedger (closed_, cachedSLEs_, journal)
|
||||
{
|
||||
memoize(master);
|
||||
Pathfinder::initPathTable();
|
||||
}
|
||||
|
||||
Env::Env(beast::unit_test::suite& test_)
|
||||
: Env(test_, makeConfig())
|
||||
{
|
||||
}
|
||||
|
||||
std::shared_ptr<OpenView const>
|
||||
Env::open() const
|
||||
{
|
||||
return openLedger.current();
|
||||
}
|
||||
|
||||
std::shared_ptr<ReadView const>
|
||||
Env::closed() const
|
||||
Env::closed()
|
||||
{
|
||||
return closed_;
|
||||
return app().getLedgerMaster().getClosedLedger();
|
||||
}
|
||||
|
||||
void
|
||||
Env::close(NetClock::time_point const& closeTime,
|
||||
OpenLedger::modify_type const& f)
|
||||
Env::close(NetClock::time_point closeTime)
|
||||
{
|
||||
clock.set(closeTime);
|
||||
// VFALCO TODO Fix the Ledger constructor
|
||||
auto next = std::make_shared<Ledger>(
|
||||
open_ledger, *closed_,
|
||||
app().timeKeeper().closeTime());
|
||||
next->setClosed();
|
||||
std::vector<std::shared_ptr<STTx const>> txs;
|
||||
auto cur = openLedger.current();
|
||||
for (auto iter = cur->txs.begin();
|
||||
iter != cur->txs.end(); ++iter)
|
||||
txs.push_back(iter->first);
|
||||
OrderedTxs retries(uint256{});
|
||||
{
|
||||
OpenView accum(&*next);
|
||||
OpenLedger::apply(app(), accum, *closed_,
|
||||
txs, retries, applyFlags(), journal);
|
||||
accum.apply(*next);
|
||||
}
|
||||
// To ensure that the close time is exact and not rounded, we don't
|
||||
// claim to have reached consensus on what it should be.
|
||||
next->setAccepted(closeTime, ledgerPossibleTimeResolutions[0],
|
||||
false, app().config());
|
||||
OrderedTxs locals({});
|
||||
openLedger.accept(app(), next->rules(), next,
|
||||
locals, false, retries, applyFlags(), "", f);
|
||||
closed_ = next;
|
||||
cachedSLEs_.expire();
|
||||
// Round up to next distinguishable value
|
||||
closeTime += closed()->info().closeTimeResolution - 1s;
|
||||
bundle_.timeKeeper->set(closeTime);
|
||||
app().getOPs().acceptLedger();
|
||||
bundle_.timeKeeper->set(
|
||||
closed()->info().closeTime);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -219,7 +199,7 @@ Env::le (Account const& account) const
|
||||
std::shared_ptr<SLE const>
|
||||
Env::le (Keylet const& k) const
|
||||
{
|
||||
return open()->read(k);
|
||||
return current()->read(k);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -232,7 +212,7 @@ Env::fund (bool setDefaultRipple,
|
||||
{
|
||||
// VFALCO NOTE Is the fee formula correct?
|
||||
apply(pay(master, account, amount +
|
||||
drops(open()->fees().base)),
|
||||
drops(current()->fees().base)),
|
||||
jtx::seq(jtx::autofill),
|
||||
fee(jtx::autofill),
|
||||
sig(jtx::autofill));
|
||||
@@ -263,7 +243,7 @@ Env::trust (STAmount const& amount,
|
||||
fee(jtx::autofill),
|
||||
sig(jtx::autofill));
|
||||
apply(pay(master, account,
|
||||
drops(open()->fees().base)),
|
||||
drops(current()->fees().base)),
|
||||
jtx::seq(jtx::autofill),
|
||||
fee(jtx::autofill),
|
||||
sig(jtx::autofill));
|
||||
@@ -277,7 +257,7 @@ Env::submit (JTx const& jt)
|
||||
if (jt.stx)
|
||||
{
|
||||
txid_ = jt.stx->getTransactionID();
|
||||
openLedger.modify(
|
||||
app().openLedger().modify(
|
||||
[&](OpenView& view, beast::Journal j)
|
||||
{
|
||||
std::tie(ter_, didApply) = ripple::apply(
|
||||
@@ -357,9 +337,9 @@ Env::autofill (JTx& jt)
|
||||
{
|
||||
auto& jv = jt.jv;
|
||||
if(jt.fill_fee)
|
||||
jtx::fill_fee(jv, *open());
|
||||
jtx::fill_fee(jv, *current());
|
||||
if(jt.fill_seq)
|
||||
jtx::fill_seq(jv, *open());
|
||||
jtx::fill_seq(jv, *current());
|
||||
// Must come last
|
||||
try
|
||||
{
|
||||
|
||||
@@ -516,14 +516,14 @@ public:
|
||||
{
|
||||
using namespace jtx;
|
||||
Env env(*this);
|
||||
auto seq = env.open()->seq();
|
||||
auto seq = env.current()->seq();
|
||||
expect(seq == env.closed()->seq() + 1);
|
||||
env.close();
|
||||
expect(env.closed()->seq() == seq);
|
||||
expect(env.open()->seq() == seq + 1);
|
||||
expect(env.current()->seq() == seq + 1);
|
||||
env.close();
|
||||
expect(env.closed()->seq() == seq + 1);
|
||||
expect(env.open()->seq() == seq + 2);
|
||||
expect(env.current()->seq() == seq + 2);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -568,7 +568,7 @@ public:
|
||||
Env env(*this);
|
||||
|
||||
env.fund(XRP(10000), "alice");
|
||||
auto const baseFee = env.open()->fees().base;
|
||||
auto const baseFee = env.current()->fees().base;
|
||||
std::uint32_t const aliceSeq = env.seq ("alice");
|
||||
|
||||
// Sign jsonNoop.
|
||||
|
||||
@@ -48,7 +48,7 @@ owned_count_helper(Env& env,
|
||||
std::uint32_t value)
|
||||
{
|
||||
env.test.expect(owned_count_of(
|
||||
*env.open(), id, type) == value);
|
||||
*env.current(), id, type) == value);
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
@@ -37,7 +37,7 @@ paths::operator()(Env& env, JTx& jt) const
|
||||
auto const amount = amountFromJson(
|
||||
sfAmount, jv[jss::Amount]);
|
||||
Pathfinder pf (
|
||||
std::make_shared<RippleLineCache>(env.open()),
|
||||
std::make_shared<RippleLineCache>(env.current()),
|
||||
from, to, in_.currency, in_.account,
|
||||
amount, boost::none, env.app());
|
||||
if (! pf.findPaths(depth_))
|
||||
|
||||
@@ -52,8 +52,7 @@ sign (Json::Value& jv,
|
||||
ss.add32 (HashPrefix::txSign);
|
||||
parse(jv).addWithoutSigningFields(ss);
|
||||
auto const sig = ripple::sign(
|
||||
*publicKeyType(account.pk().slice()),
|
||||
account.sk(), ss.slice());
|
||||
account.pk(), account.sk(), ss.slice());
|
||||
jv[jss::TxnSignature] =
|
||||
strHex(Slice{ sig.data(), sig.size() });
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
#include <BeastConfig.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/test/mao/Net.h>
|
||||
#include <ripple/test/jtx.h>
|
||||
#include <ripple/test/ManualTimeKeeper.h>
|
||||
#include <ripple/net/HTTPClient.h>
|
||||
#include <ripple/net/RPCCall.h>
|
||||
#include <beast/unit_test/suite.h>
|
||||
@@ -38,21 +40,13 @@ struct TestApp
|
||||
{
|
||||
auto config = std::make_unique<Config>();
|
||||
setupConfigForUnitTests(*config);
|
||||
config->RUN_STANDALONE = true;
|
||||
(*config)["server"].append("port_peer");
|
||||
(*config)["port_peer"].set("ip", "127.0.0.1");
|
||||
(*config)["port_peer"].set("port", "8080");
|
||||
(*config)["port_peer"].set("protocol", "peer");
|
||||
(*config)["server"].append("port_admin");
|
||||
(*config)["port_admin"].set("ip", "127.0.0.1");
|
||||
(*config)["port_admin"].set("port", "8081");
|
||||
(*config)["port_admin"].set("protocol", "http");
|
||||
(*config)["port_admin"].set("admin", "127.0.0.1");
|
||||
// Hack so we dont have to call Config::setup
|
||||
HTTPClient::initializeSSLContext(*config);
|
||||
auto logs = std::make_unique<Logs>();
|
||||
instance = make_Application(
|
||||
std::move(config), std::move(logs));
|
||||
auto timeKeeper = std::make_unique<ManualTimeKeeper>();
|
||||
timeKeeper_ = timeKeeper.get();
|
||||
instance = make_Application(std::move(config),
|
||||
std::move(logs), std::move(timeKeeper));
|
||||
instance->setup();
|
||||
thread_ = std::thread(
|
||||
[&]() { instance->run(); });
|
||||
@@ -106,6 +100,7 @@ private:
|
||||
collect(v, args...);
|
||||
}
|
||||
|
||||
ManualTimeKeeper* timeKeeper_;
|
||||
std::unique_ptr<Application> instance;
|
||||
std::thread thread_;
|
||||
std::mutex mutex_;
|
||||
|
||||
@@ -48,3 +48,4 @@
|
||||
#include <ripple/test/mao/impl/Net.cpp>
|
||||
#include <ripple/test/mao/impl/Net_test.cpp>
|
||||
|
||||
#include <ripple/test/impl/ManualTimeKeeper.cpp>
|
||||
|
||||
Reference in New Issue
Block a user