Refactor Env for AbstractClient:

Env is changed to use the AbstractClient interface,
which generalizes the transport for submitting client
requests to the Env server instance.

The JSONRPCClient implementation is added, supporting
a simple, synchronous interface. Env is changed to
use the JSONRPCClient implementation instead of the
built in JSON-RPC client.
This commit is contained in:
Vinnie Falco
2016-01-25 18:27:40 -05:00
committed by Nik Bougalis
parent f9f2b8124d
commit 040d7ebb46
9 changed files with 349 additions and 51 deletions

View File

@@ -3406,16 +3406,24 @@
</ClCompile>
<ClInclude Include="..\..\src\ripple\shamap\TreeNodeCache.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\test\AbstractClient.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\test\BasicNetwork.h">
</ClInclude>
<ClCompile Include="..\..\src\ripple\test\impl\BasicNetwork_test.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\ripple\test\impl\JSONRPCClient.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<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\JSONRPCClient.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\test\jtx.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\test\jtx\Account.h">

View File

@@ -3915,15 +3915,24 @@
<ClInclude Include="..\..\src\ripple\shamap\TreeNodeCache.h">
<Filter>ripple\shamap</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\test\AbstractClient.h">
<Filter>ripple\test</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\test\BasicNetwork.h">
<Filter>ripple\test</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ripple\test\impl\BasicNetwork_test.cpp">
<Filter>ripple\test\impl</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\test\impl\JSONRPCClient.cpp">
<Filter>ripple\test\impl</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\test\impl\ManualTimeKeeper.cpp">
<Filter>ripple\test\impl</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ripple\test\JSONRPCClient.h">
<Filter>ripple\test</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\test\jtx.h">
<Filter>ripple\test</Filter>
</ClInclude>

View File

@@ -74,13 +74,11 @@ struct Transaction_ordering_test : public beast::unit_test::suite
{
auto const result = env.rpc("tx", to_string(tx1.stx->getTransactionID()));
expect(result.first == rpcSUCCESS);
expect(result.second["result"]["meta"]["TransactionResult"] == "tesSUCCESS");
expect(result["result"]["meta"]["TransactionResult"] == "tesSUCCESS");
}
{
auto const result = env.rpc("tx", to_string(tx2.stx->getTransactionID()));
expect(result.first == rpcSUCCESS);
expect(result.second["result"]["meta"]["TransactionResult"] == "tesSUCCESS");
expect(result["result"]["meta"]["TransactionResult"] == "tesSUCCESS");
}
}
@@ -112,13 +110,11 @@ struct Transaction_ordering_test : public beast::unit_test::suite
{
auto const result = env.rpc("tx", to_string(tx1.stx->getTransactionID()));
expect(result.first == rpcSUCCESS);
expect(result.second["result"]["meta"]["TransactionResult"] == "tesSUCCESS");
expect(result["result"]["meta"]["TransactionResult"] == "tesSUCCESS");
}
{
auto const result = env.rpc("tx", to_string(tx2.stx->getTransactionID()));
expect(result.first == rpcSUCCESS);
expect(result.second["result"]["meta"]["TransactionResult"] == "tesSUCCESS");
expect(result["result"]["meta"]["TransactionResult"] == "tesSUCCESS");
}
}
@@ -160,8 +156,7 @@ struct Transaction_ordering_test : public beast::unit_test::suite
for (auto i = 0; i < 5; ++i)
{
auto const result = env.rpc("tx", to_string(tx[i].stx->getTransactionID()));
expect(result.first == rpcSUCCESS);
expect(result.second["result"]["meta"]["TransactionResult"] == "tesSUCCESS");
expect(result["result"]["meta"]["TransactionResult"] == "tesSUCCESS");
}
}

View File

@@ -0,0 +1,58 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2016 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_ABSTRACTCLIENT_H_INCLUDED
#define RIPPLE_TEST_ABSTRACTCLIENT_H_INCLUDED
#include <ripple/json/json_value.h>
namespace ripple {
namespace test {
/* Abstract Ripple Client interface.
This abstracts the transport layer, allowing
commands to be submitted to a rippled server.
*/
class AbstractClient
{
public:
virtual ~AbstractClient() = default;
/** Submit a command synchronously.
The arguments to the function and the returned JSON
are in a normalized format, the same whether the client
is using the JSON-RPC over HTTP/S or WebSocket transport.
@param cmd The command to execute
@param params Json::Value of null or object type
with zero or more key/value pairs.
@return The server response in normalized format.
*/
virtual
Json::Value
invoke(std::string const& cmd,
Json::Value const& params = {}) = 0;
};
} // test
} // ripple
#endif

View File

@@ -0,0 +1,37 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2016 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_HTTPCLIENT_H_INCLUDED
#define RIPPLE_TEST_HTTPCLIENT_H_INCLUDED
#include <ripple/test/AbstractClient.h>
#include <ripple/core/Config.h>
#include <memory>
namespace ripple {
namespace test {
/** Returns a client using JSON-RPC over HTTP/S. */
std::unique_ptr<AbstractClient>
makeJSONRPCClient(Config const& cfg);
} // test
} // ripple
#endif

View File

@@ -0,0 +1,164 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2016 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/JSONRPCClient.h>
#include <ripple/json/json_reader.h>
#include <ripple/json/to_string.h>
#include <ripple/server/Port.h>
#include <beast/asio/streambuf.h>
#include <beast/http/parser.h>
#include <boost/asio.hpp>
#include <string>
namespace ripple {
namespace test {
class JSONRPCClient : public AbstractClient
{
static
boost::asio::ip::tcp::endpoint
getEndpoint(BasicConfig const& cfg)
{
auto& log = std::cerr;
ParsedPort common;
parse_Port (common, cfg["server"], log);
for (auto const& name : cfg.section("server").values())
{
if (! cfg.exists(name))
continue;
ParsedPort pp;
parse_Port(pp, cfg[name], log);
if(pp.protocol.count("http") == 0)
continue;
using boost::asio::ip::address_v4;
if(*pp.ip == address_v4{0x00000000})
*pp.ip = address_v4{0x7f000001};
return { *pp.ip, *pp.port };
}
throw std::runtime_error("Missing HTTP port");
}
template <class ConstBufferSequence>
static
std::string
buffer_string (ConstBufferSequence const& b)
{
using namespace boost::asio;
std::string s;
s.resize(buffer_size(b));
buffer_copy(buffer(&s[0], s.size()), b);
return s;
}
boost::asio::io_service ios_;
boost::asio::ip::tcp::socket stream_;
boost::asio::streambuf bin_;
beast::asio::streambuf bout_;
public:
explicit
JSONRPCClient(Config const& cfg)
: stream_(ios_)
{
stream_.connect(getEndpoint(cfg));
}
~JSONRPCClient() override
{
//stream_.shutdown(boost::asio::ip::tcp::socket::shutdown_both);
//stream_.close();
}
/*
Return value is an Object type with up to three keys:
status
error
result
*/
Json::Value
invoke(std::string const& cmd,
Json::Value const& params) override
{
std::string s;
{
Json::Value jr;
jr["method"] = cmd;
if(params)
{
Json::Value& ja = jr["params"] = Json::arrayValue;
ja.append(params);
}
s = to_string(jr);
}
using namespace boost::asio;
using namespace std::string_literals;
std::string r;
r =
"POST / HTTP/1.1\r\n"
"Host: me\r\n"
"Connection: Keep-Alive\r\n"s +
"Content-Type: application/json; charset=UTF-8\r\n"s +
"Content-Length: " + std::to_string(s.size()) + "\r\n"
"\r\n";
write(stream_, buffer(r));
write(stream_, buffer(s));
read_until(stream_, bin_, "\r\n\r\n");
beast::asio::streambuf body;
beast::http::message m;
beast::http::parser p(
[&](void const* data, std::size_t size)
{
body.commit(buffer_copy(
body.prepare(size), const_buffer(data, size)));
}, m, false);
for(;;)
{
auto const result = p.write(bin_.data());
if (result.first)
throw result.first;
bin_.consume(result.second);
if(p.complete())
break;
bin_.commit(stream_.read_some(
bin_.prepare(1024)));
}
Json::Reader jr;
Json::Value jv;
jr.parse(buffer_string(body.data()), jv);
if(jv["result"].isMember("error"))
jv["error"] = jv["result"]["error"];
if(jv["result"].isMember("status"))
jv["status"] = jv["result"]["status"];
return jv;
}
};
std::unique_ptr<AbstractClient>
makeJSONRPCClient(Config const& cfg)
{
return std::make_unique<JSONRPCClient>(cfg);
}
} // test
} // ripple

View File

@@ -25,6 +25,7 @@
#include <ripple/test/jtx/JTx.h>
#include <ripple/test/jtx/require.h>
#include <ripple/test/jtx/tags.h>
#include <ripple/test/AbstractClient.h>
#include <ripple/test/ManualTimeKeeper.h>
#include <ripple/app/main/Application.h>
#include <ripple/app/ledger/Ledger.h>
@@ -36,7 +37,6 @@
#include <ripple/json/json_value.h>
#include <ripple/json/to_string.h>
#include <ripple/ledger/CachedSLEs.h>
#include <ripple/net/RPCCall.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/Issue.h>
#include <ripple/protocol/STAmount.h>
@@ -99,6 +99,7 @@ private:
std::unique_ptr<Application> owned;
ManualTimeKeeper* timeKeeper;
std::thread thread;
std::unique_ptr<AbstractClient> client;
AppBundle (std::unique_ptr<Config> config);
AppBundle (Application* app_);
@@ -161,7 +162,6 @@ public:
{
}
Application&
app()
{
@@ -185,6 +185,22 @@ public:
return app().timeKeeper().now();
}
/** Returns the connected client. */
AbstractClient&
client()
{
return *bundle_.client;
}
/** Execute an RPC command.
The command is examined and used to build
the correct JSON as per the arguments.
*/
template<class... Args>
Json::Value
rpc(std::string const& cmd, Args&&... args);
/** Returns the current ledger.
This is a non-modifiable snapshot of the
@@ -423,11 +439,6 @@ public:
std::shared_ptr<STObject const>
meta();
/** Execute a client command */
template <class Arg, class... Args>
std::pair<int, Json::Value>
rpc (Arg&& arg0, Args&&... args);
private:
void
fund (bool setDefaultRipple,
@@ -534,6 +545,9 @@ protected:
uint256 txid_;
TER ter_ = tesSUCCESS;
Json::Value
do_rpc(std::vector<std::string> const& args);
void
autofill_sig (JTx& jt);
@@ -628,16 +642,13 @@ protected:
AccountID, Account> map_;
};
//------------------------------------------------------------------------------
template <class Arg, class... Args>
std::pair<int, Json::Value>
Env::rpc (Arg&& arg0, Args&&... args)
template<class... Args>
Json::Value
Env::rpc(std::string const& cmd, Args&&... args)
{
std::vector<std::string> v({ std::forward<Arg>(arg0),
std::forward<Args>(args)... });
return rpcClient(v,
app().config(), app().logs());
std::vector<std::string> vs{cmd,
std::forward<Args>(args)...};
return do_rpc(vs);
}
} // jtx

View File

@@ -28,6 +28,7 @@
#include <ripple/test/jtx/seq.h>
#include <ripple/test/jtx/sig.h>
#include <ripple/test/jtx/utility.h>
#include <ripple/test/JSONRPCClient.h>
#include <ripple/app/ledger/LedgerMaster.h>
#include <ripple/app/ledger/LedgerTiming.h>
#include <ripple/app/misc/NetworkOPs.h>
@@ -37,6 +38,7 @@
#include <ripple/core/ConfigSections.h>
#include <ripple/json/to_string.h>
#include <ripple/net/HTTPClient.h>
#include <ripple/net/RPCCall.h>
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/HashPrefix.h>
#include <ripple/protocol/Indexes.h>
@@ -54,26 +56,29 @@ namespace ripple {
namespace test {
void
setupConfigForUnitTests (Config& config)
setupConfigForUnitTests (Config& cfg)
{
config.overwrite (ConfigSection::nodeDatabase (), "type", "memory");
config.overwrite (ConfigSection::nodeDatabase (), "path", "main");
config.deprecatedClearSection (ConfigSection::importNodeDatabase ());
config.legacy("database_path", "");
config.RUN_STANDALONE = true;
config.QUIET = true;
config.SILENT = 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");
cfg.overwrite (ConfigSection::nodeDatabase (), "type", "memory");
cfg.overwrite (ConfigSection::nodeDatabase (), "path", "main");
cfg.deprecatedClearSection (ConfigSection::importNodeDatabase ());
cfg.legacy("database_path", "");
cfg.RUN_STANDALONE = true;
cfg.QUIET = true;
cfg.SILENT = true;
cfg["server"].append("port_peer");
cfg["port_peer"].set("ip", "127.0.0.1");
cfg["port_peer"].set("port", "8080");
cfg["port_peer"].set("protocol", "peer");
cfg["server"].append("port_http");
cfg["port_http"].set("ip", "127.0.0.1");
cfg["port_http"].set("port", "8081");
cfg["port_http"].set("protocol", "http");
cfg["port_http"].set("admin", "127.0.0.1");
cfg["server"].append("port_ws");
cfg["port_ws"].set("ip", "127.0.0.1");
cfg["port_ws"].set("port", "8082");
cfg["port_ws"].set("protocol", "ws");
cfg["port_ws"].set("admin", "127.0.0.1");
}
//------------------------------------------------------------------------------
@@ -102,10 +107,13 @@ Env::AppBundle::AppBundle(std::unique_ptr<Config> config)
app->doStart();
thread = std::thread(
[&](){ app->run(); });
client = makeJSONRPCClient(app->config());
}
Env::AppBundle::~AppBundle()
{
client.reset();
app->signalStop();
thread.join();
}
@@ -131,8 +139,8 @@ Env::close(NetClock::time_point closeTime,
app().getOPs().acceptLedger(consensusDelay);
else
{
auto const result = rpc("ledger_accept");
test.expect(result.first == rpcSUCCESS);
rpc("ledger_accept");
// VFALCO No error check?
}
bundle_.timeKeeper->set(
closed()->info().closeTime);
@@ -274,11 +282,10 @@ Env::submit (JTx const& jt)
txid_ = jt.stx->getTransactionID();
Serializer s;
jt.stx->add(s);
auto const result = rpc("submit", strHex(s.slice()));
if (result.first == rpcSUCCESS &&
result.second["result"].isMember("engine_result_code"))
auto const jr = rpc("submit", strHex(s.slice()));
if (jr["result"].isMember("engine_result_code"))
ter_ = static_cast<TER>(
result.second["result"]["engine_result_code"].asInt());
jr["result"]["engine_result_code"].asInt());
else
ter_ = temINVALID;
didApply = isTesSuccess(ter_) || isTecClaim(ter_);
@@ -408,6 +415,14 @@ Env::applyFlags() const
return flags;
}
Json::Value
Env::do_rpc(std::vector<std::string> const& args)
{
auto const jv = cmdLineToJSONRPC(args, journal);
return client().invoke(jv["method"].asString(),
jv["params"][0U]);
}
} // jtx
} // test

View File

@@ -48,4 +48,5 @@
#include <ripple/test/mao/impl/Net.cpp>
#include <ripple/test/impl/BasicNetwork_test.cpp>
#include <ripple/test/impl/JSONRPCClient.cpp>
#include <ripple/test/impl/ManualTimeKeeper.cpp>