rippled
ETLSource.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/reporting/ETLSource.h>
21 #include <ripple/app/reporting/ReportingETL.h>
22 #include <ripple/beast/core/CurrentThreadName.h>
23 #include <ripple/json/json_reader.h>
24 #include <ripple/json/json_writer.h>
25 
26 namespace ripple {
27 
28 // Create ETL source without grpc endpoint
29 // Fetch ledger and load initial ledger will fail for this source
30 // Primarly used in read-only mode, to monitor when ledgers are validated
32  : ip_(ip)
33  , wsPort_(wsPort)
34  , etl_(etl)
35  , ioc_(etl.getApplication().getIOService())
36  , ws_(std::make_unique<
37  boost::beast::websocket::stream<boost::beast::tcp_stream>>(
38  boost::asio::make_strand(ioc_)))
39  , resolver_(boost::asio::make_strand(ioc_))
40  , networkValidatedLedgers_(etl_.getNetworkValidatedLedgers())
41  , journal_(etl_.getApplication().journal("ReportingETL::ETLSource"))
42  , app_(etl_.getApplication())
43  , timer_(ioc_)
44 {
45 }
46 
48  std::string ip,
49  std::string wsPort,
50  std::string grpcPort,
51  ReportingETL& etl)
52  : ip_(ip)
53  , wsPort_(wsPort)
54  , grpcPort_(grpcPort)
55  , etl_(etl)
56  , ioc_(etl.getApplication().getIOService())
57  , ws_(std::make_unique<
58  boost::beast::websocket::stream<boost::beast::tcp_stream>>(
59  boost::asio::make_strand(ioc_)))
60  , resolver_(boost::asio::make_strand(ioc_))
61  , networkValidatedLedgers_(etl_.getNetworkValidatedLedgers())
62  , journal_(etl_.getApplication().journal("ReportingETL::ETLSource"))
63  , app_(etl_.getApplication())
64  , timer_(ioc_)
65 {
66  try
67  {
68  stub_ = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub(
69  grpc::CreateChannel(
71  boost::asio::ip::make_address(ip_), std::stoi(grpcPort_))
72  .to_string(),
73  grpc::InsecureChannelCredentials()));
74  JLOG(journal_.info()) << "Made stub for remote = " << toString();
75  }
76  catch (std::exception const& e)
77  {
78  JLOG(journal_.error()) << "Exception while creating stub = " << e.what()
79  << " . Remote = " << toString();
80  }
81 }
82 
83 void
84 ETLSource::reconnect(boost::beast::error_code ec)
85 {
86  connected_ = false;
87  // These are somewhat normal errors. operation_aborted occurs on shutdown,
88  // when the timer is cancelled. connection_refused will occur repeatedly
89  // if we cannot connect to the transaction processing process
90  if (ec != boost::asio::error::operation_aborted &&
91  ec != boost::asio::error::connection_refused)
92  {
93  JLOG(journal_.error()) << __func__ << " : "
94  << "error code = " << ec << " - " << toString();
95  }
96  else
97  {
98  JLOG(journal_.warn()) << __func__ << " : "
99  << "error code = " << ec << " - " << toString();
100  }
101 
102  if (etl_.isStopping())
103  {
104  JLOG(journal_.debug()) << __func__ << " : " << toString()
105  << " - etl is stopping. aborting reconnect";
106  return;
107  }
108 
109  // exponentially increasing timeouts, with a max of 30 seconds
110  size_t waitTime = std::min(pow(2, numFailures_), 30.0);
111  numFailures_++;
112  timer_.expires_after(boost::asio::chrono::seconds(waitTime));
113  timer_.async_wait([this](auto ec) {
114  bool startAgain = (ec != boost::asio::error::operation_aborted);
115  JLOG(journal_.trace()) << __func__ << " async_wait : ec = " << ec;
116  close(startAgain);
117  });
118 }
119 
120 void
121 ETLSource::close(bool startAgain)
122 {
123  timer_.cancel();
124  ioc_.post([this, startAgain]() {
125  if (closing_)
126  return;
127 
128  if (ws_->is_open())
129  {
130  // onStop() also calls close(). If the async_close is called twice,
131  // an assertion fails. Using closing_ makes sure async_close is only
132  // called once
133  closing_ = true;
134  ws_->async_close(
135  boost::beast::websocket::close_code::normal,
136  [this, startAgain](auto ec) {
137  if (ec)
138  {
139  JLOG(journal_.error())
140  << __func__ << " async_close : "
141  << "error code = " << ec << " - " << toString();
142  }
143  closing_ = false;
144  if (startAgain)
145  start();
146  });
147  }
148  else if (startAgain)
149  {
150  start();
151  }
152  });
153 }
154 
155 void
156 ETLSource::start()
157 {
158  JLOG(journal_.trace()) << __func__ << " : " << toString();
159 
160  auto const host = ip_;
161  auto const port = wsPort_;
162 
163  resolver_.async_resolve(
164  host, port, [this](auto ec, auto results) { onResolve(ec, results); });
165 }
166 
167 void
168 ETLSource::onResolve(
169  boost::beast::error_code ec,
170  boost::asio::ip::tcp::resolver::results_type results)
171 {
172  JLOG(journal_.trace()) << __func__ << " : ec = " << ec << " - "
173  << toString();
174  if (ec)
175  {
176  // try again
177  reconnect(ec);
178  }
179  else
180  {
181  boost::beast::get_lowest_layer(*ws_).expires_after(
183  boost::beast::get_lowest_layer(*ws_).async_connect(
184  results, [this](auto ec, auto ep) { onConnect(ec, ep); });
185  }
186 }
187 
188 void
189 ETLSource::onConnect(
190  boost::beast::error_code ec,
191  boost::asio::ip::tcp::resolver::results_type::endpoint_type endpoint)
192 {
193  JLOG(journal_.trace()) << __func__ << " : ec = " << ec << " - "
194  << toString();
195  if (ec)
196  {
197  // start over
198  reconnect(ec);
199  }
200  else
201  {
202  numFailures_ = 0;
203  // Turn off timeout on the tcp stream, because websocket stream has it's
204  // own timeout system
205  boost::beast::get_lowest_layer(*ws_).expires_never();
206 
207  // Set suggested timeout settings for the websocket
208  ws_->set_option(
209  boost::beast::websocket::stream_base::timeout::suggested(
210  boost::beast::role_type::client));
211 
212  // Set a decorator to change the User-Agent of the handshake
213  ws_->set_option(boost::beast::websocket::stream_base::decorator(
214  [](boost::beast::websocket::request_type& req) {
215  req.set(
216  boost::beast::http::field::user_agent,
217  std::string(BOOST_BEAST_VERSION_STRING) +
218  " websocket-client-async");
219  }));
220 
221  // Update the host_ string. This will provide the value of the
222  // Host HTTP header during the WebSocket handshake.
223  // See https://tools.ietf.org/html/rfc7230#section-5.4
224  auto host = ip_ + ':' + std::to_string(endpoint.port());
225  // Perform the websocket handshake
226  ws_->async_handshake(host, "/", [this](auto ec) { onHandshake(ec); });
227  }
228 }
229 
230 void
231 ETLSource::onHandshake(boost::beast::error_code ec)
232 {
233  JLOG(journal_.trace()) << __func__ << " : ec = " << ec << " - "
234  << toString();
235  if (ec)
236  {
237  // start over
238  reconnect(ec);
239  }
240  else
241  {
242  Json::Value jv;
243  jv["command"] = "subscribe";
244 
245  jv["streams"] = Json::arrayValue;
246  Json::Value ledgerStream("ledger");
247  jv["streams"].append(ledgerStream);
248  Json::Value txnStream("transactions_proposed");
249  jv["streams"].append(txnStream);
250  Json::FastWriter fastWriter;
251 
252  JLOG(journal_.trace()) << "Sending subscribe stream message";
253  // Send the message
254  ws_->async_write(
255  boost::asio::buffer(fastWriter.write(jv)),
256  [this](auto ec, size_t size) { onWrite(ec, size); });
257  }
258 }
259 
260 void
261 ETLSource::onWrite(boost::beast::error_code ec, size_t bytesWritten)
262 {
263  JLOG(journal_.trace()) << __func__ << " : ec = " << ec << " - "
264  << toString();
265  if (ec)
266  {
267  // start over
268  reconnect(ec);
269  }
270  else
271  {
272  ws_->async_read(
273  readBuffer_, [this](auto ec, size_t size) { onRead(ec, size); });
274  }
275 }
276 
277 void
278 ETLSource::onRead(boost::beast::error_code ec, size_t size)
279 {
280  JLOG(journal_.trace()) << __func__ << " : ec = " << ec << " - "
281  << toString();
282  // if error or error reading message, start over
283  if (ec)
284  {
285  reconnect(ec);
286  }
287  else
288  {
289  handleMessage();
290  boost::beast::flat_buffer buffer;
291  swap(readBuffer_, buffer);
292 
293  JLOG(journal_.trace())
294  << __func__ << " : calling async_read - " << toString();
295  ws_->async_read(
296  readBuffer_, [this](auto ec, size_t size) { onRead(ec, size); });
297  }
298 }
299 
300 bool
301 ETLSource::handleMessage()
302 {
303  JLOG(journal_.trace()) << __func__ << " : " << toString();
304 
305  setLastMsgTime();
306  connected_ = true;
307  try
308  {
309  Json::Value response;
310  Json::Reader reader;
311  if (!reader.parse(
312  static_cast<char const*>(readBuffer_.data().data()), response))
313  {
314  JLOG(journal_.error())
315  << __func__ << " : "
316  << "Error parsing stream message."
317  << " Message = " << readBuffer_.data().data();
318  return false;
319  }
320 
321  uint32_t ledgerIndex = 0;
322  if (response.isMember("result"))
323  {
324  if (response["result"].isMember(jss::ledger_index))
325  {
326  ledgerIndex = response["result"][jss::ledger_index].asUInt();
327  }
328  if (response[jss::result].isMember(jss::validated_ledgers))
329  {
330  setValidatedRange(
331  response[jss::result][jss::validated_ledgers].asString());
332  }
333  JLOG(journal_.debug())
334  << __func__ << " : "
335  << "Received a message on ledger "
336  << " subscription stream. Message : "
337  << response.toStyledString() << " - " << toString();
338  }
339  else
340  {
341  if (response.isMember(jss::transaction))
342  {
343  if (etl_.getETLLoadBalancer().shouldPropagateTxnStream(this))
344  {
345  etl_.getApplication().getOPs().forwardProposedTransaction(
346  response);
347  }
348  }
349  else
350  {
351  JLOG(journal_.debug())
352  << __func__ << " : "
353  << "Received a message on ledger "
354  << " subscription stream. Message : "
355  << response.toStyledString() << " - " << toString();
356  if (response.isMember(jss::ledger_index))
357  {
358  ledgerIndex = response[jss::ledger_index].asUInt();
359  }
360  if (response.isMember(jss::validated_ledgers))
361  {
362  setValidatedRange(
363  response[jss::validated_ledgers].asString());
364  }
365  }
366  }
367 
368  if (ledgerIndex != 0)
369  {
370  JLOG(journal_.trace())
371  << __func__ << " : "
372  << "Pushing ledger sequence = " << ledgerIndex << " - "
373  << toString();
374  networkValidatedLedgers_.push(ledgerIndex);
375  }
376  return true;
377  }
378  catch (std::exception const& e)
379  {
380  JLOG(journal_.error()) << "Exception in handleMessage : " << e.what();
381  return false;
382  }
383 }
384 
386 {
389 
390  org::xrpl::rpc::v1::GetLedgerDataRequest request_;
392 
393  grpc::Status status_;
394 
395  unsigned char nextPrefix_;
396 
398 
399 public:
401  uint256& marker,
402  std::optional<uint256> nextMarker,
403  uint32_t seq,
404  beast::Journal& j)
405  : journal_(j)
406  {
407  request_.mutable_ledger()->set_sequence(seq);
408  if (marker.isNonZero())
409  {
410  request_.set_marker(marker.data(), marker.size());
411  }
412  request_.set_user("ETL");
413  nextPrefix_ = 0x00;
414  if (nextMarker)
415  nextPrefix_ = nextMarker->data()[0];
416 
417  unsigned char prefix = marker.data()[0];
418 
419  JLOG(journal_.debug())
420  << "Setting up AsyncCallData. marker = " << strHex(marker)
421  << " . prefix = " << strHex(std::string(1, prefix))
422  << " . nextPrefix_ = " << strHex(std::string(1, nextPrefix_));
423 
424  assert(nextPrefix_ > prefix || nextPrefix_ == 0x00);
425 
426  cur_ = std::make_unique<org::xrpl::rpc::v1::GetLedgerDataResponse>();
427 
428  next_ = std::make_unique<org::xrpl::rpc::v1::GetLedgerDataResponse>();
429 
430  context_ = std::make_unique<grpc::ClientContext>();
431  }
432 
433  enum class CallStatus { MORE, DONE, ERRORED };
434  CallStatus
437  grpc::CompletionQueue& cq,
439  bool abort = false)
440  {
441  JLOG(journal_.debug()) << "Processing calldata";
442  if (abort)
443  {
444  JLOG(journal_.error()) << "AsyncCallData aborted";
445  return CallStatus::ERRORED;
446  }
447  if (!status_.ok())
448  {
449  JLOG(journal_.debug()) << "AsyncCallData status_ not ok: "
450  << " code = " << status_.error_code()
451  << " message = " << status_.error_message();
452  return CallStatus::ERRORED;
453  }
454  if (!next_->is_unlimited())
455  {
456  JLOG(journal_.warn())
457  << "AsyncCallData is_unlimited is false. Make sure "
458  "secure_gateway is set correctly at the ETL source";
459  assert(false);
460  }
461 
462  std::swap(cur_, next_);
463 
464  bool more = true;
465 
466  // if no marker returned, we are done
467  if (cur_->marker().size() == 0)
468  more = false;
469 
470  // if returned marker is greater than our end, we are done
471  unsigned char prefix = cur_->marker()[0];
472  if (nextPrefix_ != 0x00 && prefix >= nextPrefix_)
473  more = false;
474 
475  // if we are not done, make the next async call
476  if (more)
477  {
478  request_.set_marker(std::move(cur_->marker()));
479  call(stub, cq);
480  }
481 
482  for (auto& obj : cur_->ledger_objects().objects())
483  {
484  auto key = uint256::fromVoid(obj.key().data());
485  auto& data = obj.data();
486 
487  SerialIter it{data.data(), data.size()};
488  std::shared_ptr<SLE> sle = std::make_shared<SLE>(it, key);
489 
490  queue.push(sle);
491  }
492 
493  return more ? CallStatus::MORE : CallStatus::DONE;
494  }
495 
496  void
499  grpc::CompletionQueue& cq)
500  {
501  context_ = std::make_unique<grpc::ClientContext>();
502 
503  std::unique_ptr<grpc::ClientAsyncResponseReader<
504  org::xrpl::rpc::v1::GetLedgerDataResponse>>
505  rpc(stub->PrepareAsyncGetLedgerData(context_.get(), request_, &cq));
506 
507  rpc->StartCall();
508 
509  rpc->Finish(next_.get(), &status_, this);
510  }
511 
514  {
515  if (next_->marker().size() == 0)
516  return "";
517  else
518  return strHex(std::string{next_->marker().data()[0]});
519  }
520 };
521 
522 bool
523 ETLSource::loadInitialLedger(
524  uint32_t sequence,
526 {
527  if (!stub_)
528  return false;
529 
530  grpc::CompletionQueue cq;
531 
532  void* tag;
533 
534  bool ok = false;
535 
537  std::vector<uint256> markers{getMarkers(etl_.getNumMarkers())};
538 
539  for (size_t i = 0; i < markers.size(); ++i)
540  {
541  std::optional<uint256> nextMarker;
542  if (i + 1 < markers.size())
543  nextMarker = markers[i + 1];
544  calls.emplace_back(markers[i], nextMarker, sequence, journal_);
545  }
546 
547  JLOG(journal_.debug()) << "Starting data download for ledger " << sequence
548  << ". Using source = " << toString();
549 
550  for (auto& c : calls)
551  c.call(stub_, cq);
552 
553  size_t numFinished = 0;
554  bool abort = false;
555  while (numFinished < calls.size() && !etl_.isStopping() &&
556  cq.Next(&tag, &ok))
557  {
558  assert(tag);
559 
560  auto ptr = static_cast<AsyncCallData*>(tag);
561 
562  if (!ok)
563  {
564  JLOG(journal_.error()) << "loadInitialLedger - ok is false";
565  return false;
566  // handle cancelled
567  }
568  else
569  {
570  JLOG(journal_.debug())
571  << "Marker prefix = " << ptr->getMarkerPrefix();
572  auto result = ptr->process(stub_, cq, writeQueue, abort);
573  if (result != AsyncCallData::CallStatus::MORE)
574  {
575  numFinished++;
576  JLOG(journal_.debug())
577  << "Finished a marker. "
578  << "Current number of finished = " << numFinished;
579  }
580  if (result == AsyncCallData::CallStatus::ERRORED)
581  {
582  abort = true;
583  }
584  }
585  }
586  return !abort;
587 }
588 
590 ETLSource::fetchLedger(uint32_t ledgerSequence, bool getObjects)
591 {
592  org::xrpl::rpc::v1::GetLedgerResponse response;
593  if (!stub_)
594  return {{grpc::StatusCode::INTERNAL, "No Stub"}, response};
595 
596  // ledger header with txns and metadata
597  org::xrpl::rpc::v1::GetLedgerRequest request;
598  grpc::ClientContext context;
599  request.mutable_ledger()->set_sequence(ledgerSequence);
600  request.set_transactions(true);
601  request.set_expand(true);
602  request.set_get_objects(getObjects);
603  request.set_user("ETL");
604  grpc::Status status = stub_->GetLedger(&context, request, &response);
605  if (status.ok() && !response.is_unlimited())
606  {
607  JLOG(journal_.warn()) << "ETLSource::fetchLedger - is_unlimited is "
608  "false. Make sure secure_gateway is set "
609  "correctly on the ETL source. source = "
610  << toString();
611  assert(false);
612  }
613  return {status, std::move(response)};
614 }
615 
616 ETLLoadBalancer::ETLLoadBalancer(ReportingETL& etl)
617  : etl_(etl)
618  , journal_(etl_.getApplication().journal("ReportingETL::LoadBalancer"))
619 {
620 }
621 
622 void
624  std::string& host,
625  std::string& websocketPort,
626  std::string& grpcPort)
627 {
629  std::make_unique<ETLSource>(host, websocketPort, grpcPort, etl_);
630  sources_.push_back(std::move(ptr));
631  JLOG(journal_.info()) << __func__ << " : added etl source - "
632  << sources_.back()->toString();
633 }
634 
635 void
637 {
639  std::make_unique<ETLSource>(host, websocketPort, etl_);
640  sources_.push_back(std::move(ptr));
641  JLOG(journal_.info()) << __func__ << " : added etl source - "
642  << sources_.back()->toString();
643 }
644 
645 void
647  uint32_t sequence,
649 {
650  execute(
651  [this, &sequence, &writeQueue](auto& source) {
652  bool res = source->loadInitialLedger(sequence, writeQueue);
653  if (!res)
654  {
655  JLOG(journal_.error()) << "Failed to download initial ledger. "
656  << " Sequence = " << sequence
657  << " source = " << source->toString();
658  }
659  return res;
660  },
661  sequence);
662 }
663 
665 ETLLoadBalancer::fetchLedger(uint32_t ledgerSequence, bool getObjects)
666 {
667  org::xrpl::rpc::v1::GetLedgerResponse response;
668  bool success = execute(
669  [&response, ledgerSequence, getObjects, this](auto& source) {
670  auto [status, data] =
671  source->fetchLedger(ledgerSequence, getObjects);
672  response = std::move(data);
673  if (status.ok() && response.validated())
674  {
675  JLOG(journal_.info())
676  << "Successfully fetched ledger = " << ledgerSequence
677  << " from source = " << source->toString();
678  return true;
679  }
680  else
681  {
682  JLOG(journal_.warn())
683  << "Error getting ledger = " << ledgerSequence
684  << " Reply : " << response.DebugString()
685  << " error_code : " << status.error_code()
686  << " error_msg : " << status.error_message()
687  << " source = " << source->toString();
688  return false;
689  }
690  },
691  ledgerSequence);
692  if (success)
693  return response;
694  else
695  return {};
696 }
697 
700 {
701  if (sources_.size() == 0)
702  return nullptr;
703  srand((unsigned)time(0));
704  auto sourceIdx = rand() % sources_.size();
705  auto numAttempts = 0;
706  while (numAttempts < sources_.size())
707  {
708  auto stub = sources_[sourceIdx]->getP2pForwardingStub();
709  if (!stub)
710  {
711  sourceIdx = (sourceIdx + 1) % sources_.size();
712  ++numAttempts;
713  continue;
714  }
715  return stub;
716  }
717  return nullptr;
718 }
719 
722 {
723  Json::Value res;
724  if (sources_.size() == 0)
725  return res;
726  srand((unsigned)time(0));
727  auto sourceIdx = rand() % sources_.size();
728  auto numAttempts = 0;
729  while (numAttempts < sources_.size())
730  {
731  res = sources_[sourceIdx]->forwardToP2p(context);
732  if (!res.isMember("forwarded") || res["forwarded"] != true)
733  {
734  sourceIdx = (sourceIdx + 1) % sources_.size();
735  ++numAttempts;
736  continue;
737  }
738  return res;
739  }
741  err.inject(res);
742  return res;
743 }
744 
747 {
748  if (!connected_)
749  return nullptr;
750  try
751  {
752  return org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub(
753  grpc::CreateChannel(
755  boost::asio::ip::make_address(ip_), std::stoi(grpcPort_))
756  .to_string(),
757  grpc::InsecureChannelCredentials()));
758  }
759  catch (std::exception const&)
760  {
761  JLOG(journal_.error()) << "Failed to create grpc stub";
762  return nullptr;
763  }
764 }
765 
768 {
769  JLOG(journal_.debug()) << "Attempting to forward request to tx. "
770  << "request = " << context.params.toStyledString();
771 
772  Json::Value response;
773  if (!connected_)
774  {
775  JLOG(journal_.error())
776  << "Attempted to proxy but failed to connect to tx";
777  return response;
778  }
779  namespace beast = boost::beast; // from <boost/beast.hpp>
780  namespace http = beast::http; // from <boost/beast/http.hpp>
781  namespace websocket = beast::websocket; // from <boost/beast/websocket.hpp>
782  namespace net = boost::asio; // from <boost/asio.hpp>
783  using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
784  Json::Value& request = context.params;
785  try
786  {
787  // The io_context is required for all I/O
788  net::io_context ioc;
789 
790  // These objects perform our I/O
791  tcp::resolver resolver{ioc};
792 
793  JLOG(journal_.debug()) << "Creating websocket";
794  auto ws = std::make_unique<websocket::stream<tcp::socket>>(ioc);
795 
796  // Look up the domain name
797  auto const results = resolver.resolve(ip_, wsPort_);
798 
799  JLOG(journal_.debug()) << "Connecting websocket";
800  // Make the connection on the IP address we get from a lookup
801  net::connect(ws->next_layer(), results.begin(), results.end());
802 
803  // Set a decorator to change the User-Agent of the handshake
804  // and to tell rippled to charge the client IP for RPC
805  // resources. See "secure_gateway" in
806  // https://github.com/ripple/rippled/blob/develop/cfg/rippled-example.cfg
807  ws->set_option(websocket::stream_base::decorator(
808  [&context](websocket::request_type& req) {
809  req.set(
810  http::field::user_agent,
811  std::string(BOOST_BEAST_VERSION_STRING) +
812  " websocket-client-coro");
813  req.set(
814  http::field::forwarded,
815  "for=" + context.consumer.to_string());
816  }));
817  JLOG(journal_.debug()) << "client ip: " << context.consumer.to_string();
818 
819  JLOG(journal_.debug()) << "Performing websocket handshake";
820  // Perform the websocket handshake
821  ws->handshake(ip_, "/");
822 
823  Json::FastWriter fastWriter;
824 
825  JLOG(journal_.debug()) << "Sending request";
826  // Send the message
827  ws->write(net::buffer(fastWriter.write(request)));
828 
829  beast::flat_buffer buffer;
830  ws->read(buffer);
831 
832  Json::Reader reader;
833  if (!reader.parse(
834  static_cast<char const*>(buffer.data().data()), response))
835  {
836  JLOG(journal_.error()) << "Error parsing response";
837  response[jss::error] = "Error parsing response from tx";
838  }
839  JLOG(journal_.debug()) << "Successfully forward request";
840 
841  response["forwarded"] = true;
842  return response;
843  }
844  catch (std::exception const& e)
845  {
846  JLOG(journal_.error()) << "Encountered exception : " << e.what();
847  return response;
848  }
849 }
850 
851 template <class Func>
852 bool
853 ETLLoadBalancer::execute(Func f, uint32_t ledgerSequence)
854 {
855  srand((unsigned)time(0));
856  auto sourceIdx = rand() % sources_.size();
857  auto numAttempts = 0;
858 
859  while (!etl_.isStopping())
860  {
861  auto& source = sources_[sourceIdx];
862 
863  JLOG(journal_.debug())
864  << __func__ << " : "
865  << "Attempting to execute func. ledger sequence = "
866  << ledgerSequence << " - source = " << source->toString();
867  if (source->hasLedger(ledgerSequence))
868  {
869  bool res = f(source);
870  if (res)
871  {
872  JLOG(journal_.debug())
873  << __func__ << " : "
874  << "Successfully executed func at source = "
875  << source->toString()
876  << " - ledger sequence = " << ledgerSequence;
877  break;
878  }
879  else
880  {
881  JLOG(journal_.warn())
882  << __func__ << " : "
883  << "Failed to execute func at source = "
884  << source->toString()
885  << " - ledger sequence = " << ledgerSequence;
886  }
887  }
888  else
889  {
890  JLOG(journal_.warn())
891  << __func__ << " : "
892  << "Ledger not present at source = " << source->toString()
893  << " - ledger sequence = " << ledgerSequence;
894  }
895  sourceIdx = (sourceIdx + 1) % sources_.size();
896  numAttempts++;
897  if (numAttempts % sources_.size() == 0)
898  {
899  // If another process loaded the ledger into the database, we can
900  // abort trying to fetch the ledger from a transaction processing
901  // process
903  ledgerSequence))
904  {
905  JLOG(journal_.warn())
906  << __func__ << " : "
907  << "Error executing function. "
908  << " Tried all sources, but ledger was found in db."
909  << " Sequence = " << ledgerSequence;
910  break;
911  }
912  JLOG(journal_.error())
913  << __func__ << " : "
914  << "Error executing function "
915  << " - ledger sequence = " << ledgerSequence
916  << " - Tried all sources. Sleeping and trying again";
918  }
919  }
920  return !etl_.isStopping();
921 }
922 
923 void
925 {
926  for (auto& source : sources_)
927  source->start();
928 }
929 
930 void
932 {
933  for (auto& source : sources_)
934  source->stop();
935 }
936 
937 } // namespace ripple
ripple::AsyncCallData::context_
std::unique_ptr< grpc::ClientContext > context_
Definition: ETLSource.cpp:391
ripple::RPC::JsonContext
Definition: Context.h:53
std::this_thread::sleep_for
T sleep_for(T... args)
std::string
STL class.
std::shared_ptr
STL class.
ripple::AsyncCallData::CallStatus
CallStatus
Definition: ETLSource.cpp:433
ripple::ThreadSafeQueue
Generic thread-safe queue with an optional maximum size Note, we can't use a lockfree queue here,...
Definition: ETLHelpers.h:105
std::exception
STL class.
ripple::base_uint::isNonZero
bool isNonZero() const
Definition: base_uint.h:444
beast::Journal::trace
Stream trace() const
Severity stream access functions.
Definition: Journal.h:309
ripple::ETLLoadBalancer::forwardToP2p
Json::Value forwardToP2p(RPC::JsonContext &context) const
Forward a JSON RPC request to a randomly selected p2p node.
Definition: ETLSource.cpp:721
ripple::ETLLoadBalancer::start
void start()
Setup all of the ETL sources and subscribe to the necessary streams.
Definition: ETLSource.cpp:924
Json::arrayValue
@ arrayValue
array value (ordered list)
Definition: json_value.h:42
std::pair
std::vector
STL class.
ripple::ETLSource::etl_
ReportingETL & etl_
Definition: ETLSource.h:54
std::chrono::seconds
ripple::ETLLoadBalancer::fetchLedger
std::optional< org::xrpl::rpc::v1::GetLedgerResponse > fetchLedger(uint32_t ledgerSequence, bool getObjects)
Fetch data for a specific ledger.
Definition: ETLSource.cpp:665
beast::Journal::warn
Stream warn() const
Definition: Journal.h:327
std::unique_ptr::get
T get(T... args)
ripple::ETLSource::ip_
std::string ip_
Definition: ETLSource.h:48
ripple::AsyncCallData::status_
grpc::Status status_
Definition: ETLSource.cpp:393
ripple::ETLSource::ETLSource
ETLSource(std::string ip, std::string wsPort, ReportingETL &etl)
Create ETL source without gRPC endpoint Fetch ledger and load initial ledger will fail for this sourc...
Definition: ETLSource.cpp:31
ripple::AsyncCallData::journal_
beast::Journal journal_
Definition: ETLSource.cpp:397
boost
Definition: IPAddress.h:117
Json::Value::toStyledString
std::string toStyledString() const
Definition: json_value.cpp:1039
ripple::to_string
std::string to_string(ListDisposition disposition)
Definition: ValidatorList.cpp:45
ripple::ReportingETL::getApplication
Application & getApplication()
Definition: ReportingETL.h:298
Json::Reader
Unserialize a JSON document into a Value.
Definition: json_reader.h:36
ripple::ETLLoadBalancer::getP2pForwardingStub
std::unique_ptr< org::xrpl::rpc::v1::XRPLedgerAPIService::Stub > getP2pForwardingStub() const
Randomly select a p2p node to forward a gRPC request to.
Definition: ETLSource.cpp:699
ripple::base_uint::data
pointer data()
Definition: base_uint.h:113
ripple::base_uint::size
constexpr static std::size_t size()
Definition: base_uint.h:426
ripple::AsyncCallData::getMarkerPrefix
std::string getMarkerPrefix()
Definition: ETLSource.cpp:513
ripple::base_uint< 256 >
ripple::Resource::Consumer::to_string
std::string to_string() const
Return a human readable string uniquely identifying this consumer.
Definition: Consumer.cpp:71
std::stoi
T stoi(T... args)
Json::Value::append
Value & append(const Value &value)
Append value to array at the end.
Definition: json_value.cpp:882
ripple::ETLLoadBalancer::journal_
beast::Journal journal_
Definition: ETLSource.h:320
ripple::RPC::Context::consumer
Resource::Consumer & consumer
Definition: Context.h:46
ripple::ETLSource::connected_
std::atomic_bool connected_
Definition: ETLSource.h:83
ripple::ETLSource::stub_
std::unique_ptr< org::xrpl::rpc::v1::XRPLedgerAPIService::Stub > stub_
Definition: ETLSource.h:59
ripple::ReportingETL::isStopping
bool isStopping()
Definition: ReportingETL.h:282
ripple::Application::getLedgerMaster
virtual LedgerMaster & getLedgerMaster()=0
ripple::ETLLoadBalancer::stop
void stop()
Definition: ETLSource.cpp:931
ripple::ETLLoadBalancer::loadInitialLedger
void loadInitialLedger(uint32_t sequence, ThreadSafeQueue< std::shared_ptr< SLE >> &writeQueue)
Load the initial ledger, writing data to the queue.
Definition: ETLSource.cpp:646
ripple::AsyncCallData::nextPrefix_
unsigned char nextPrefix_
Definition: ETLSource.cpp:395
ripple::ETLSource::reconnect
void reconnect(boost::beast::error_code ec)
Attempt to reconnect to the ETL source.
Definition: ETLSource.cpp:84
ripple::AsyncCallData::AsyncCallData
AsyncCallData(uint256 &marker, std::optional< uint256 > nextMarker, uint32_t seq, beast::Journal &j)
Definition: ETLSource.cpp:400
ripple::ETLLoadBalancer::execute
bool execute(Func f, uint32_t ledgerSequence)
f is a function that takes an ETLSource as an argument and returns a bool.
Definition: ETLSource.cpp:853
std::to_string
T to_string(T... args)
ripple::AsyncCallData::cur_
std::unique_ptr< org::xrpl::rpc::v1::GetLedgerDataResponse > cur_
Definition: ETLSource.cpp:387
boost::asio
Definition: Overlay.h:42
beast::Journal::error
Stream error() const
Definition: Journal.h:333
beast::Journal::info
Stream info() const
Definition: Journal.h:321
ripple::AsyncCallData::process
CallStatus process(std::unique_ptr< org::xrpl::rpc::v1::XRPLedgerAPIService::Stub > &stub, grpc::CompletionQueue &cq, ThreadSafeQueue< std::shared_ptr< SLE >> &queue, bool abort=false)
Definition: ETLSource.cpp:435
ripple::LedgerMaster::getLedgerBySeq
std::shared_ptr< Ledger const > getLedgerBySeq(std::uint32_t index)
Definition: LedgerMaster.cpp:1740
ripple::ReportingETL
This class is responsible for continuously extracting data from a p2p node, and writing that data to ...
Definition: ReportingETL.h:70
ripple::ETLSource::grpcPort_
std::string grpcPort_
Definition: ETLSource.h:52
ripple::SerialIter
Definition: Serializer.h:308
Json::Value::isMember
bool isMember(const char *key) const
Return true if the object has a member named key.
Definition: json_value.cpp:932
beast::Journal
A generic endpoint for log messages.
Definition: Journal.h:58
ripple::AsyncCallData
Definition: ETLSource.cpp:385
Json::FastWriter::write
std::string write(const Value &root) override
Definition: json_writer.cpp:193
ripple::AsyncCallData::call
void call(std::unique_ptr< org::xrpl::rpc::v1::XRPLedgerAPIService::Stub > &stub, grpc::CompletionQueue &cq)
Definition: ETLSource.cpp:497
ripple::RPC::Status::inject
void inject(Object &object) const
Apply the Status to a JsonObject.
Definition: Status.h:115
ripple::RPC::Status
Status represents the results of an operation that might fail.
Definition: Status.h:39
ripple::ETLLoadBalancer::sources_
std::vector< std::unique_ptr< ETLSource > > sources_
Definition: ETLSource.h:322
ripple::ETLSource::journal_
beast::Journal journal_
Definition: ETLSource.h:73
ripple::ETLSource::getP2pForwardingStub
std::unique_ptr< org::xrpl::rpc::v1::XRPLedgerAPIService::Stub > getP2pForwardingStub() const
Get grpc stub to forward requests to p2p node.
Definition: ETLSource.cpp:746
std::swap
T swap(T... args)
std::min
T min(T... args)
ripple::ETLSource::numFailures_
size_t numFailures_
Definition: ETLSource.h:79
ripple::ETLSource::ioc_
boost::asio::io_context & ioc_
Definition: ETLSource.h:57
std::vector::emplace_back
T emplace_back(T... args)
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::ETLSource::start
void start()
Begin sequence of operations to connect to the ETL source and subscribe to ledgers and transactions_p...
Definition: ETLSource.cpp:156
ripple::ETLSource::ws_
std::unique_ptr< boost::beast::websocket::stream< boost::beast::tcp_stream > > ws_
Definition: ETLSource.h:62
std
STL namespace.
ripple::ETLSource::closing_
std::atomic_bool closing_
Definition: ETLSource.h:81
ripple::ETLLoadBalancer::etl_
ReportingETL & etl_
Definition: ETLSource.h:318
Json::Value::asUInt
UInt asUInt() const
Definition: json_value.cpp:545
Json::FastWriter
Outputs a Value in JSON format without formatting (not human friendly).
Definition: json_writer.h:52
Json::Reader::parse
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
Definition: json_reader.cpp:74
ripple::rpcFAILED_TO_FORWARD
@ rpcFAILED_TO_FORWARD
Definition: ErrorCodes.h:140
ripple::AsyncCallData::next_
std::unique_ptr< org::xrpl::rpc::v1::GetLedgerDataResponse > next_
Definition: ETLSource.cpp:388
ripple::ETLSource::toString
std::string toString() const
Definition: ETLSource.h:222
std::optional
beast::Journal::debug
Stream debug() const
Definition: Journal.h:315
beast::IP::Endpoint
A version-independent IP address and port combination.
Definition: IPEndpoint.h:39
ripple::strHex
std::string strHex(FwdIt begin, FwdIt end)
Definition: strHex.h:45
ripple::ETLSource::close
void close(bool startAgain)
Close the websocket.
Definition: ETLSource.cpp:121
ripple::AsyncCallData::request_
org::xrpl::rpc::v1::GetLedgerDataRequest request_
Definition: ETLSource.cpp:390
std::unique_ptr< org::xrpl::rpc::v1::GetLedgerDataResponse >
ripple::ETLSource::timer_
boost::asio::steady_timer timer_
Definition: ETLSource.h:96
ripple::RPC::JsonContext::params
Json::Value params
Definition: Context.h:64
std::exception::what
T what(T... args)
ripple::ETLSource::forwardToP2p
Json::Value forwardToP2p(RPC::JsonContext &context) const
Forward a JSON RPC request to a p2p node.
Definition: ETLSource.cpp:767
ripple::getMarkers
std::vector< uint256 > getMarkers(size_t numMarkers)
Parititions the uint256 keyspace into numMarkers partitions, each of equal size.
Definition: ETLHelpers.h:167
Json::Value
Represents a JSON value.
Definition: json_value.h:145
ripple::ETLSource::wsPort_
std::string wsPort_
Definition: ETLSource.h:50
beast
Definition: base_uint.h:585
ripple::ETLLoadBalancer::add
void add(std::string &host, std::string &websocketPort, std::string &grpcPort)
Add an ETL source.
Definition: ETLSource.cpp:623