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