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