Skip to content

Commit

Permalink
multiprocess: Add echoipc RPC method and test
Browse files Browse the repository at this point in the history
Add simple interfaces::Echo IPC interface with one method that just takes and
returns a string, to test multiprocess framework and provide an example of how
it can be used to spawn and call between processes.
  • Loading branch information
ryanofsky committed Jun 3, 2020
1 parent 9df8470 commit 885f0d1
Show file tree
Hide file tree
Showing 15 changed files with 156 additions and 0 deletions.
5 changes: 5 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ BITCOIN_CORE_H = \
init.h \
interfaces/base.h \
interfaces/chain.h \
interfaces/echo.h \
interfaces/handler.h \
interfaces/init.h \
interfaces/node.h \
Expand Down Expand Up @@ -513,6 +514,7 @@ libbitcoin_util_a_SOURCES = \
compat/strnlen.cpp \
fs.cpp \
interfaces/base.cpp \
interfaces/echo.cpp \
interfaces/init.cpp \
interfaces/handler.cpp \
logging.cpp \
Expand Down Expand Up @@ -733,13 +735,16 @@ if HARDEN
endif

libbitcoin_ipc_mpgen_input = \
interfaces/capnp/echo.capnp \
interfaces/capnp/init.capnp
EXTRA_DIST += $(libbitcoin_ipc_mpgen_input)
%.capnp:

if BUILD_MULTIPROCESS
LIBBITCOIN_IPC=libbitcoin_ipc.a
libbitcoin_ipc_a_SOURCES = \
interfaces/capnp/echo-types.h \
interfaces/capnp/echo.h \
interfaces/capnp/init-types.h \
interfaces/capnp/init.cpp \
interfaces/capnp/init.h \
Expand Down
7 changes: 7 additions & 0 deletions src/interfaces/capnp/echo-types.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Copyright (c) 2020 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#ifndef BITCOIN_INTERFACES_CAPNP_ECHO_TYPES_H
#define BITCOIN_INTERFACES_CAPNP_ECHO_TYPES_H
#endif // BITCOIN_INTERFACES_CAPNP_ECHO_TYPES_H
15 changes: 15 additions & 0 deletions src/interfaces/capnp/echo.capnp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Copyright (c) 2020 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.

@0x888b4f7f51e691f7;

using Cxx = import "/capnp/c++.capnp";
$Cxx.namespace("interfaces::capnp::messages");

using Proxy = import "/mp/proxy.capnp";

interface Echo $Proxy.wrap("interfaces::Echo") {
destroy @0 (context :Proxy.Context) -> ();
echo @1 (context :Proxy.Context, echo: Text) -> (result :Text);
}
11 changes: 11 additions & 0 deletions src/interfaces/capnp/echo.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright (c) 2020 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#ifndef BITCOIN_INTERFACES_CAPNP_ECHO_H
#define BITCOIN_INTERFACES_CAPNP_ECHO_H

#include <interfaces/capnp/echo.capnp.h>
#include <interfaces/echo.h>

#endif // BITCOIN_INTERFACES_CAPNP_ECHO_H
3 changes: 3 additions & 0 deletions src/interfaces/capnp/init-types.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@

#ifndef BITCOIN_INTERFACES_CAPNP_INIT_TYPES_H
#define BITCOIN_INTERFACES_CAPNP_INIT_TYPES_H

#include <interfaces/capnp/echo.capnp.proxy-types.h>

#endif // BITCOIN_INTERFACES_CAPNP_INIT_TYPES_H
2 changes: 2 additions & 0 deletions src/interfaces/capnp/init.capnp
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
using Cxx = import "/capnp/c++.capnp";
$Cxx.namespace("interfaces::capnp::messages");

using Echo = import "echo.capnp";
using Proxy = import "/mp/proxy.capnp";

interface Init $Proxy.wrap("interfaces::Init") {
construct @0 (threadMap: Proxy.ThreadMap) -> (threadMap :Proxy.ThreadMap);
makeEcho @1 (context :Proxy.Context) -> (result :Echo.Echo);
}
1 change: 1 addition & 0 deletions src/interfaces/capnp/init.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#ifndef BITCOIN_INTERFACES_CAPNP_INIT_H
#define BITCOIN_INTERFACES_CAPNP_INIT_H

#include <interfaces/echo.h>
#include <interfaces/init.h>
#include <mp/proxy.h>

Expand Down
23 changes: 23 additions & 0 deletions src/interfaces/echo.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) 2020 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <interfaces/echo.h>

#include <util/memory.h>

namespace interfaces {
namespace {
class EchoImpl : public Echo
{
public:
std::string echo(const std::string& echo) override
{
return echo;
}
};
} // namespace

std::unique_ptr<Echo> MakeEcho() { return MakeUnique<EchoImpl>(); }

} // namespace interfaces
29 changes: 29 additions & 0 deletions src/interfaces/echo.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) 2020 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#ifndef BITCOIN_INTERFACES_ECHO_H
#define BITCOIN_INTERFACES_ECHO_H

#include <interfaces/base.h>

#include <string>

namespace interfaces {

//! Simple string echoing interface for testing.
class Echo : public Base
{
public:
virtual ~Echo() {}

//! Echo provided string.
virtual std::string echo(const std::string& echo) = 0;
};

//! Return implementation of Echo interface.
std::unique_ptr<Echo> MakeEcho();

} // namespace interfaces

#endif // BITCOIN_INTERFACES_ECHO_H
3 changes: 3 additions & 0 deletions src/interfaces/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <interfaces/init.h>

#include <interfaces/chain.h>
#include <interfaces/echo.h>
#include <interfaces/ipc.h>
#include <interfaces/node.h>
#include <logging.h>
Expand All @@ -28,6 +29,8 @@ class CloseFn : public CloseHook

LocalInit::LocalInit(const char* exe_name, const char* log_suffix) : m_exe_name(exe_name), m_log_suffix(log_suffix) {}
LocalInit::~LocalInit() {}
std::unique_ptr<Echo> LocalInit::makeEcho() { return {}; }
std::unique_ptr<Echo> LocalInit::makeEchoIpc() { return {}; }
NodeContext& LocalInit::node()
{
throw std::logic_error("Node accessor function called from non-node binary (gui, wallet, or test program)");
Expand Down
6 changes: 6 additions & 0 deletions src/interfaces/init.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ struct NodeContext;

namespace interfaces {
class Base;
class Echo;
class IpcProcess;
class IpcProtocol;

Expand All @@ -25,6 +26,7 @@ class Init
{
public:
virtual ~Init() = default;
virtual std::unique_ptr<Echo> makeEcho() = 0;
};

//! Specialization of Init for current process.
Expand All @@ -33,8 +35,12 @@ class LocalInit : public Init
public:
LocalInit(const char* exe_name, const char* log_suffix);
~LocalInit() override;
std::unique_ptr<Echo> makeEcho() override;
using MakeClientFn = std::function<Base&(Init&)>;
void spawnProcess(const std::string& new_exe_name, const MakeClientFn& make_client);
//! Make echo implementation for `echoipc` test RPC. Spawn new process if
//! supported.
virtual std::unique_ptr<Echo> makeEchoIpc();
virtual NodeContext& node();
const char* m_exe_name;
const char* m_log_suffix;
Expand Down
18 changes: 18 additions & 0 deletions src/interfaces/init_bitcoin-node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <interfaces/init.h>

#include <interfaces/capnp/ipc.h>
#include <interfaces/echo.h>
#include <node/context.h>
#include <util/memory.h>

Expand All @@ -20,6 +21,23 @@ class LocalInitImpl : public LocalInit
m_protocol = capnp::MakeCapnpProtocol(m_exe_name, *this);
m_process = MakeIpcProcess(argc, argv, m_exe_name, *m_protocol);
}
std::unique_ptr<Echo> makeEcho() override { return MakeEcho(); }
std::unique_ptr<Echo> makeEchoIpc() override
{
// Spawn a new bitcoin-node process and call makeEcho to get a client
// pointer to a interfaces::Echo instance running in that process. This
// is just for testing. A slightly more realistic test would build a new
// bitcoin-echo executable, and spawn bitcoin-echo here instead of
// bitcoin-node. But using bitcoin-node avoids the need to build and
// install a new binary not useful for anything except testing
// multiprocess support.
std::unique_ptr<Echo> echo;
spawnProcess("bitcoin-node", [&](Init& init) -> Base& {
echo = init.makeEcho();
return *echo;
});
return echo;
}
NodeContext& node() override { return m_node; };
NodeContext m_node;
};
Expand Down
11 changes: 11 additions & 0 deletions src/interfaces/init_bitcoind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include <interfaces/init.h>

#include <interfaces/echo.h>
#include <node/context.h>
#include <util/memory.h>

Expand All @@ -17,6 +18,16 @@ class LocalInitImpl : public LocalInit
m_node.init = this;
m_request_context.Set(m_node);
}
std::unique_ptr<Echo> makeEchoIpc() override
{
// The bitcoind binary isn't linked against libmultiprocess and doesn't
// have IPC support, so just create a local interfaces::Echo object and
// return it so the `echoipc` RPC method will work, and the python test
// calling `echoipc` doesn't have to care whether it is testing a
// bitcoind process without IPC support, or a bitcoin-node process with
// IPC support.
return MakeEcho();
}
NodeContext& node() override { return m_node; };
NodeContext m_node;
};
Expand Down
21 changes: 21 additions & 0 deletions src/rpc/misc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

#include <httpserver.h>
#include <interfaces/chain.h>
#include <interfaces/echo.h>
#include <interfaces/init.h>
#include <key_io.h>
#include <node/context.h>
#include <outputtype.h>
Expand Down Expand Up @@ -597,6 +599,24 @@ static UniValue echo(const JSONRPCRequest& request)
return request.params;
}

static UniValue echoipc(const JSONRPCRequest& request)
{
RPCHelpMan{
"echoipc",
"\nEcho back the input argument, passing it through a spawned process in a multiprocess build.\n"
"This command is for testing.\n",
{{"echo", RPCArg::Type::STR, RPCArg::Optional::NO, "The string to echo",}},
RPCResult{RPCResult::Type::STR, "echo", "The echoed string."},
RPCExamples{HelpExampleCli("echo", "\"Hello world\"") +
HelpExampleRpc("echo", "\"Hello world\"")},
}.Check(request);

NodeContext* context = request.context.Has<NodeContext>() ? &request.context.Get<NodeContext>() : nullptr;
if (!context || !context->init) throw JSONRPCError(RPC_INTERNAL_ERROR, "IPC not available");
std::unique_ptr<interfaces::Echo> echo = context->init->makeEchoIpc();
return echo->echo(request.params[0].get_str());
}

void RegisterMiscRPCCommands(CRPCTable &t)
{
// clang-format off
Expand All @@ -617,6 +637,7 @@ static const CRPCCommand commands[] =
{ "hidden", "mockscheduler", &mockscheduler, {"delta_time"}},
{ "hidden", "echo", &echo, {"arg0","arg1","arg2","arg3","arg4","arg5","arg6","arg7","arg8","arg9"}},
{ "hidden", "echojson", &echo, {"arg0","arg1","arg2","arg3","arg4","arg5","arg6","arg7","arg8","arg9"}},
{ "hidden", "echoipc", &echoipc, {"arg"}},
};
// clang-format on

Expand Down
1 change: 1 addition & 0 deletions test/functional/rpc_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ def run_test(self):
node.logging(include=['qt'])
assert_equal(node.logging()['qt'], True)

assert_equal(node.echoipc("hello"), "hello")

if __name__ == '__main__':
RpcMiscTest().main()

0 comments on commit 885f0d1

Please sign in to comment.