rippled
GRPCServer.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2020 Ripple Labs Inc.
5 
6  Permission to use, copy, modify, and/or distribute this software for any
7  purpose with or without fee is hereby granted, provided that the above
8  copyright notice and this permission notice appear in all copies.
9 
10  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18 //==============================================================================
19 
20 #include <ripple/app/main/GRPCServer.h>
21 #include <ripple/resource/Fees.h>
22 
23 namespace ripple {
24 
25 namespace {
26 
27 // helper function. strips scheme from endpoint string
29 getEndpoint(std::string const& peer)
30 {
31  std::size_t first = peer.find_first_of(":");
32  std::size_t last = peer.find_last_of(":");
33  std::string peerClean(peer);
34  if (first != last)
35  {
36  peerClean = peer.substr(first + 1);
37  }
38  return peerClean;
39 }
40 } // namespace
41 
42 template <class Request, class Response>
44  org::xrpl::rpc::v1::XRPLedgerAPIService::AsyncService& service,
45  grpc::ServerCompletionQueue& cq,
46  Application& app,
49  RPC::Condition requiredCondition,
50  Resource::Charge loadType)
51  : service_(service)
52  , cq_(cq)
53  , finished_(false)
54  , app_(app)
55  , responder_(&ctx_)
56  , bindListener_(std::move(bindListener))
57  , handler_(std::move(handler))
58  , requiredCondition_(std::move(requiredCondition))
59  , loadType_(std::move(loadType))
60 {
61  // Bind a listener. When a request is received, "this" will be returned
62  // from CompletionQueue::Next
64 }
65 
66 template <class Request, class Response>
69 {
70  return std::make_shared<CallData<Request, Response>>(
71  service_,
72  cq_,
73  app_,
74  bindListener_,
75  handler_,
76  requiredCondition_,
77  loadType_);
78 }
79 
80 template <class Request, class Response>
81 void
83 {
84  // sanity check
85  BOOST_ASSERT(!finished_);
86 
88  this->shared_from_this();
89 
90  // Need to set finished to true before processing the response,
91  // because as soon as the response is posted to the completion
92  // queue (via responder_.Finish(...) or responder_.FinishWithError(...)),
93  // the CallData object is returned as a tag in handleRpcs().
94  // handleRpcs() checks the finished variable, and if true, destroys
95  // the object. Setting finished to true before calling process
96  // ensures that finished is always true when this CallData object
97  // is returned as a tag in handleRpcs(), after sending the response
98  finished_ = true;
99  auto coro = app_.getJobQueue().postCoro(
100  JobType::jtRPC,
101  "gRPC-Client",
102  [thisShared](std::shared_ptr<JobQueue::Coro> coro) {
103 
104  thisShared->process(coro);
105  });
106 
107  // If coro is null, then the JobQueue has already been shutdown
108  if (!coro)
109  {
110  grpc::Status status{grpc::StatusCode::INTERNAL,
111  "Job Queue is already stopped"};
112  responder_.FinishWithError(status, this);
113  }
114 }
115 
116 template <class Request, class Response>
117 void
120 {
121  try
122  {
123  auto usage = getUsage();
124  if (usage.disconnect())
125  {
126  grpc::Status status{grpc::StatusCode::RESOURCE_EXHAUSTED,
127  "usage balance exceeds threshhold"};
128  responder_.FinishWithError(status, this);
129  }
130  else
131  {
132  auto loadType = getLoadType();
133  usage.charge(loadType);
134  auto role = getRole();
135 
136  RPC::GRPCContext<Request> context{{app_.journal("gRPCServer"),
137  app_,
138  loadType,
139  app_.getOPs(),
141  usage,
142  role,
143  coro,
144  InfoSub::pointer()},
145  request_};
146 
147  // Make sure we can currently handle the rpc
148  error_code_i conditionMetRes =
149  RPC::conditionMet(requiredCondition_, context);
150 
151  if (conditionMetRes != rpcSUCCESS)
152  {
153  RPC::ErrorInfo errorInfo = RPC::get_error_info(conditionMetRes);
154  grpc::Status status{grpc::StatusCode::FAILED_PRECONDITION,
155  errorInfo.message.c_str()};
156  responder_.FinishWithError(status, this);
157  }
158  else
159  {
160  std::pair<Response, grpc::Status> result = handler_(context);
161  responder_.Finish(result.first, result.second, this);
162  }
163  }
164  }
165  catch (std::exception const& ex)
166  {
167  grpc::Status status{grpc::StatusCode::INTERNAL, ex.what()};
168  responder_.FinishWithError(status, this);
169  }
170 }
171 
172 template <class Request, class Response>
173 bool
175 {
176  return finished_;
177 }
178 
179 template <class Request, class Response>
182 {
183  return loadType_;
184 }
185 
186 template <class Request, class Response>
187 Role
189 {
190  return Role::USER;
191 }
192 
193 template <class Request, class Response>
196 {
197  std::string peer = getEndpoint(ctx_.peer());
198  boost::optional<beast::IP::Endpoint> endpoint =
200  return app_.getResourceManager().newInboundEndpoint(endpoint.get());
201 }
202 
204  : app_(app), journal_(app_.journal("gRPC Server"))
205 {
206  // if present, get endpoint from config
207  if (app_.config().exists("port_grpc"))
208  {
209  Section section = app_.config().section("port_grpc");
210 
211  std::pair<std::string, bool> ipPair = section.find("ip");
212  if (!ipPair.second)
213  return;
214 
215  std::pair<std::string, bool> portPair = section.find("port");
216  if (!portPair.second)
217  return;
218  try
219  {
220  beast::IP::Endpoint endpoint(
221  boost::asio::ip::make_address(ipPair.first),
222  std::stoi(portPair.first));
223 
224  serverAddress_ = endpoint.to_string();
225  }
226  catch (std::exception const&)
227  {
228  }
229  }
230 }
231 
232 void
234 {
235  JLOG(journal_.debug()) << "Shutting down";
236 
237  //The below call cancels all "listeners" (CallData objects that are waiting
238  //for a request, as opposed to processing a request), and blocks until all
239  //requests being processed are completed. CallData objects in the midst of
240  //processing requests need to actually send data back to the client, via
241  //responder_.Finish(...) or responder_.FinishWithError(...), for this call
242  //to unblock. Each cancelled listener is returned via cq_.Next(...) with ok
243  //set to false
244  server_->Shutdown();
245  JLOG(journal_.debug()) << "Server has been shutdown";
246 
247  // Always shutdown the completion queue after the server. This call allows
248  // cq_.Next() to return false, once all events posted to the completion
249  // queue have been processed. See handleRpcs() for more details.
250  cq_->Shutdown();
251  JLOG(journal_.debug()) << "Completion Queue has been shutdown";
252 
253 }
254 
255 void
257 {
258  // This collection should really be an unordered_set. However, to delete
259  // from the unordered_set, we need a shared_ptr, but cq_.Next() (see below
260  // while loop) sets the tag to a raw pointer.
262 
263  auto erase = [&requests](Processor* ptr) {
264  auto it = std::find_if(
265  requests.begin(),
266  requests.end(),
267  [ptr](std::shared_ptr<Processor>& sPtr) {
268  return sPtr.get() == ptr;
269  });
270  BOOST_ASSERT(it != requests.end());
271  it->swap(requests.back());
272  requests.pop_back();
273  };
274 
275  void* tag; // uniquely identifies a request.
276  bool ok;
277  // Block waiting to read the next event from the completion queue. The
278  // event is uniquely identified by its tag, which in this case is the
279  // memory address of a CallData instance.
280  // The return value of Next should always be checked. This return value
281  // tells us whether there is any kind of event or cq_ is shutting down.
282  // When cq_.Next(...) returns false, all work has been completed and the
283  // loop can exit. When the server is shutdown, each CallData object that is
284  // listening for a request is forceably cancelled, and is returned by
285  // cq_->Next() with ok set to false. Then, each CallData object processing
286  // a request must complete (by sending data to the client), each of which
287  // will be returned from cq_->Next() with ok set to true. After all
288  // cancelled listeners and all CallData objects processing requests are
289  // returned via cq_->Next(), cq_->Next() will return false, causing the
290  // loop to exit.
291  while (cq_->Next(&tag, &ok))
292  {
293  auto ptr = static_cast<Processor*>(tag);
294  JLOG(journal_.trace()) << "Processing CallData object."
295  << " ptr = " << ptr
296  << " ok = " << ok;
297 
298  if (!ok)
299  {
300  JLOG(journal_.debug()) << "Request listener cancelled. "
301  << "Destroying object";
302  erase(ptr);
303  }
304  else
305  {
306  if (!ptr->isFinished())
307  {
308  JLOG(journal_.debug()) << "Received new request. Processing";
309  // ptr is now processing a request, so create a new CallData
310  // object to handle additional requests
311  auto cloned = ptr->clone();
312  requests.push_back(cloned);
313  // process the request
314  ptr->process();
315  }
316  else
317  {
318 
319  JLOG(journal_.debug()) << "Sent response. Destroying object";
320  erase(ptr);
321  }
322  }
323  }
324  JLOG(journal_.debug()) << "Completion Queue drained";
325 }
326 
327 // create a CallData instance for each RPC
330 {
332 
333  auto addToRequests = [&requests](auto callData) {
334  requests.push_back(std::move(callData));
335  };
336 
337  {
339 
340  addToRequests(std::make_shared<cd>(
341  service_,
342  *cq_,
343  app_,
344  &org::xrpl::rpc::v1::XRPLedgerAPIService::AsyncService::RequestGetFee,
345  doFeeGrpc,
348  }
349  {
350  using cd = CallData<
351  org::xrpl::rpc::v1::GetAccountInfoRequest,
352  org::xrpl::rpc::v1::GetAccountInfoResponse>;
353 
354  addToRequests(std::make_shared<cd>(
355  service_,
356  *cq_,
357  app_,
358  &org::xrpl::rpc::v1::XRPLedgerAPIService::AsyncService::RequestGetAccountInfo,
362  }
363  {
364  using cd = CallData<
365  org::xrpl::rpc::v1::GetTransactionRequest,
366  org::xrpl::rpc::v1::GetTransactionResponse>;
367 
368  addToRequests(std::make_shared<cd>(
369  service_,
370  *cq_,
371  app_,
372  &org::xrpl::rpc::v1::XRPLedgerAPIService::AsyncService::RequestGetTransaction,
373  doTxGrpc,
376  }
377  {
378  using cd = CallData<
379  org::xrpl::rpc::v1::SubmitTransactionRequest,
380  org::xrpl::rpc::v1::SubmitTransactionResponse>;
381 
382  addToRequests(std::make_shared<cd>(
383  service_,
384  *cq_,
385  app_,
386  &org::xrpl::rpc::v1::XRPLedgerAPIService::AsyncService::
387  RequestSubmitTransaction,
388  doSubmitGrpc,
391  }
392 
393  {
394  using cd = CallData<
395  org::xrpl::rpc::v1::GetAccountTransactionHistoryRequest,
396  org::xrpl::rpc::v1::GetAccountTransactionHistoryResponse>;
397 
398  addToRequests(std::make_shared<cd>(
399  service_,
400  *cq_,
401  app_,
402  &org::xrpl::rpc::v1::XRPLedgerAPIService::AsyncService::
403  RequestGetAccountTransactionHistory,
407  }
408  return requests;
409 };
410 
411 bool
413 {
414  // if config does not specify a grpc server address, don't start
415  if (serverAddress_.empty())
416  return false;
417 
418  JLOG(journal_.info()) << "Starting gRPC server at " << serverAddress_;
419 
420  grpc::ServerBuilder builder;
421  // Listen on the given address without any authentication mechanism.
422  builder.AddListeningPort(serverAddress_, grpc::InsecureServerCredentials());
423  // Register "service_" as the instance through which we'll communicate with
424  // clients. In this case it corresponds to an *asynchronous* service.
425  builder.RegisterService(&service_);
426  // Get hold of the completion queue used for the asynchronous communication
427  // with the gRPC runtime.
428  cq_ = builder.AddCompletionQueue();
429  // Finally assemble the server.
430  server_ = builder.BuildAndStart();
431 
432  return true;
433 }
434 
435 void
437 {
438  // Start the server and setup listeners
439  if ((running_ = impl_.start()))
440  {
441  thread_ = std::thread([this]() {
442  // Start the event loop and begin handling requests
443  this->impl_.handleRpcs();
444  });
445  }
446 }
447 
449 {
450  if (running_)
451  {
452  impl_.shutdown();
453  thread_.join();
454  }
455 }
456 
457 } // namespace ripple
ripple::Resource::Manager::newInboundEndpoint
virtual Consumer newInboundEndpoint(beast::IP::Endpoint const &address)=0
Create a new endpoint keyed by inbound IP address or the forwarded IP if proxied.
ripple::Section
Holds a collection of configuration values.
Definition: BasicConfig.h:43
ripple::Application
Definition: Application.h:85
ripple::Processor
Definition: GRPCServer.h:41
std::string
STL class.
std::shared_ptr
STL class.
ripple::JobQueue::postCoro
std::shared_ptr< Coro > postCoro(JobType t, std::string const &name, F &&f)
Creates a coroutine and adds a job to the queue which will run it.
Definition: JobQueue.h:395
ripple::GRPCServer::impl_
GRPCServerImpl impl_
Definition: GRPCServer.h:251
std::exception
STL class.
beast::Journal::trace
Stream trace() const
Severity stream access functions.
Definition: Journal.h:287
ripple::GRPCServerImpl::CallData::clone
std::shared_ptr< Processor > clone() override
Definition: GRPCServer.cpp:68
ripple::RPC::get_error_info
ErrorInfo const & get_error_info(error_code_i code)
Returns an ErrorInfo that reflects the error code.
Definition: ErrorCodes.cpp:175
ripple::Resource::feeMediumBurdenRPC
const Charge feeMediumBurdenRPC
beast::IP::Endpoint::to_string
std::string to_string() const
Returns a string representing the endpoint.
Definition: IPEndpoint.cpp:54
std::pair
ripple::GRPCServerImpl::start
bool start()
Definition: GRPCServer.cpp:412
std::vector
STL class.
std::find_if
T find_if(T... args)
ripple::GRPCServerImpl::shutdown
void shutdown()
Definition: GRPCServer.cpp:233
ripple::GRPCServerImpl::CallData::request_
Request request_
Definition: GRPCServer.h:163
ripple::GRPCServerImpl::service_
org::xrpl::rpc::v1::XRPLedgerAPIService::AsyncService service_
Definition: GRPCServer.h:80
ripple::doAccountTxGrpc
std::pair< org::xrpl::rpc::v1::GetAccountTransactionHistoryResponse, grpc::Status > doAccountTxGrpc(RPC::GRPCContext< org::xrpl::rpc::v1::GetAccountTransactionHistoryRequest > &context)
Definition: AccountTx.cpp:569
ripple::GRPCServerImpl::CallData::cq_
grpc::ServerCompletionQueue & cq_
Definition: GRPCServer.h:146
ripple::GRPCServer::running_
bool running_
Definition: GRPCServer.h:253
ripple::GRPCServerImpl::server_
std::unique_ptr< grpc::Server > server_
Definition: GRPCServer.h:82
ripple::Resource::feeReferenceRPC
const Charge feeReferenceRPC
std::vector::back
T back(T... args)
std::function
ripple::GRPCServerImpl::handleRpcs
void handleRpcs()
Definition: GRPCServer.cpp:256
Json::StaticString::c_str
constexpr const char * c_str() const
Definition: json_value.h:75
ripple::GRPCServerImpl::CallData::isFinished
virtual bool isFinished() override
Definition: GRPCServer.cpp:174
ripple::Application::getOPs
virtual NetworkOPs & getOPs()=0
ripple::error_code_i
error_code_i
Definition: ErrorCodes.h:40
std::vector::push_back
T push_back(T... args)
ripple::RPC::ErrorInfo::message
Json::StaticString message
Definition: ErrorCodes.h:176
ripple::erase
void erase(STObject &st, TypedField< U > const &f)
Remove a field in an STObject.
Definition: STExchange.h:201
ripple::GRPCServer::thread_
std::thread thread_
Definition: GRPCServer.h:252
ripple::GRPCServerImpl::CallData::service_
org::xrpl::rpc::v1::XRPLedgerAPIService::AsyncService & service_
Definition: GRPCServer.h:143
std::stoi
T stoi(T... args)
ripple::rpcSUCCESS
@ rpcSUCCESS
Definition: ErrorCodes.h:45
ripple::GRPCServerImpl::cq_
std::unique_ptr< grpc::ServerCompletionQueue > cq_
Definition: GRPCServer.h:75
ripple::GRPCServerImpl::CallData::getUsage
Resource::Consumer getUsage()
Definition: GRPCServer.cpp:195
std::thread
STL class.
ripple::InfoSub::pointer
std::shared_ptr< InfoSub > pointer
Definition: InfoSub.h:46
ripple::Application::getLedgerMaster
virtual LedgerMaster & getLedgerMaster()=0
ripple::Role::USER
@ USER
ripple::RPC::NEEDS_CURRENT_LEDGER
@ NEEDS_CURRENT_LEDGER
Definition: Handler.h:42
ripple::Application::config
virtual Config & config()=0
ripple::RPC::GRPCContext
Definition: Context.h:70
std::string::find_last_of
T find_last_of(T... args)
ripple::Application::getJobQueue
virtual JobQueue & getJobQueue()=0
ripple::doTxGrpc
std::pair< org::xrpl::rpc::v1::GetTransactionResponse, grpc::Status > doTxGrpc(RPC::GRPCContext< org::xrpl::rpc::v1::GetTransactionRequest > &context)
Definition: Tx.cpp:394
ripple::RPC::NO_CONDITION
@ NO_CONDITION
Definition: Handler.h:40
beast::Journal::info
Stream info() const
Definition: Journal.h:297
ripple::GRPCServerImpl::GRPCServerImpl
GRPCServerImpl(Application &app)
Definition: GRPCServer.cpp:203
ripple::RPC::Condition
Condition
Definition: Handler.h:39
ripple::RPC::ErrorInfo
Maps an rpc error code to its token and default message.
Definition: ErrorCodes.h:158
ripple::GRPCServerImpl::CallData::getLoadType
Resource::Charge getLoadType()
Definition: GRPCServer.cpp:181
std::vector::pop_back
T pop_back(T... args)
ripple::Section::find
std::pair< std::string, bool > find(std::string const &name) const
Retrieve a key/value pair.
Definition: BasicConfig.cpp:115
ripple::GRPCServerImpl::CallData::bindListener_
BindListener< Request, Response > bindListener_
Definition: GRPCServer.h:172
ripple::Application::getResourceManager
virtual Resource::Manager & getResourceManager()=0
std::string::substr
T substr(T... args)
ripple::GRPCServer::~GRPCServer
~GRPCServer()
Definition: GRPCServer.cpp:448
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::GRPCServerImpl::setupListeners
std::vector< std::shared_ptr< Processor > > setupListeners()
Definition: GRPCServer.cpp:329
ripple::Application::journal
virtual beast::Journal journal(std::string const &name)=0
ripple::GRPCServerImpl::CallData::ctx_
grpc::ServerContext ctx_
Definition: GRPCServer.h:151
ripple::RPC::conditionMet
error_code_i conditionMet(Condition condition_required, T &context)
Definition: Handler.h:73
std::vector::begin
T begin(T... args)
std
STL namespace.
ripple::Resource::Consumer
An endpoint that consumes resources.
Definition: Consumer.h:33
ripple::Resource::Charge
A consumption charge.
Definition: Charge.h:30
ripple::GRPCServerImpl::CallData
Definition: GRPCServer.h:136
std::string::empty
T empty(T... args)
ripple::GRPCServer::run
void run()
Definition: GRPCServer.cpp:436
std::string::find_first_of
T find_first_of(T... args)
beast::Journal::debug
Stream debug() const
Definition: Journal.h:292
std::size_t
ripple::doFeeGrpc
std::pair< org::xrpl::rpc::v1::GetFeeResponse, grpc::Status > doFeeGrpc(RPC::GRPCContext< org::xrpl::rpc::v1::GetFeeRequest > &context)
Definition: Fee1.cpp:42
beast::IP::Endpoint
A version-independent IP address and port combination.
Definition: IPEndpoint.h:39
ripple::GRPCServerImpl::app_
Application & app_
Definition: GRPCServer.h:84
std::vector::end
T end(T... args)
beast::IP::Endpoint::from_string_checked
static boost::optional< Endpoint > from_string_checked(std::string const &s)
Create an Endpoint from a string.
Definition: IPEndpoint.cpp:37
ripple::GRPCServerImpl::CallData::getRole
Role getRole()
Definition: GRPCServer.cpp:188
ripple::GRPCServerImpl::journal_
beast::Journal journal_
Definition: GRPCServer.h:88
ripple::Role
Role
Indicates the level of administrative permission to grant.
Definition: Role.h:40
ripple::GRPCServerImpl::CallData::CallData
CallData(org::xrpl::rpc::v1::XRPLedgerAPIService::AsyncService &service, grpc::ServerCompletionQueue &cq, Application &app, BindListener< Request, Response > bindListener, Handler< Request, Response > handler, RPC::Condition requiredCondition, Resource::Charge loadType)
Definition: GRPCServer.cpp:43
ripple::GRPCServerImpl::CallData::process
virtual void process() override
Definition: GRPCServer.cpp:82
ripple::GRPCServerImpl::serverAddress_
std::string serverAddress_
Definition: GRPCServer.h:86
std::thread::join
T join(T... args)
std::exception::what
T what(T... args)
ripple::doAccountInfoGrpc
std::pair< org::xrpl::rpc::v1::GetAccountInfoResponse, grpc::Status > doAccountInfoGrpc(RPC::GRPCContext< org::xrpl::rpc::v1::GetAccountInfoRequest > &context)
Definition: AccountInfo.cpp:189
ripple::BasicConfig::exists
bool exists(std::string const &name) const
Returns true if a section with the given name exists.
Definition: BasicConfig.cpp:134
ripple::GRPCServerImpl::CallData::responder_
grpc::ServerAsyncResponseWriter< Response > responder_
Definition: GRPCServer.h:169
ripple::BasicConfig::section
Section & section(std::string const &name)
Returns the section with the given name.
Definition: BasicConfig.cpp:140
ripple::doSubmitGrpc
std::pair< org::xrpl::rpc::v1::SubmitTransactionResponse, grpc::Status > doSubmitGrpc(RPC::GRPCContext< org::xrpl::rpc::v1::SubmitTransactionRequest > &context)
Definition: Submit.cpp:188