xrpld
Loading...
Searching...
No Matches
PeerImp.cpp
1#include <xrpld/overlay/detail/PeerImp.h>
2
3#include <xrpld/app/consensus/RCLCxPeerPos.h>
4#include <xrpld/app/consensus/RCLValidations.h>
5#include <xrpld/app/ledger/InboundLedgers.h>
6#include <xrpld/app/ledger/InboundTransactions.h>
7#include <xrpld/app/ledger/LedgerMaster.h>
8#include <xrpld/app/ledger/LedgerNodeHelpers.h>
9#include <xrpld/app/ledger/TransactionMaster.h>
10#include <xrpld/app/misc/Transaction.h>
11#include <xrpld/app/misc/ValidatorList.h>
12#include <xrpld/overlay/Cluster.h>
13#include <xrpld/overlay/ClusterNode.h>
14#include <xrpld/overlay/Peer.h>
15#include <xrpld/overlay/ReduceRelayCommon.h>
16#include <xrpld/overlay/detail/Handshake.h>
17#include <xrpld/overlay/detail/OverlayImpl.h>
18#include <xrpld/overlay/detail/ProtocolMessage.h>
19#include <xrpld/overlay/detail/ProtocolVersion.h>
20#include <xrpld/overlay/detail/TrafficCount.h>
21#include <xrpld/overlay/detail/Tuning.h>
22
23#include <xrpl/basics/Blob.h>
24#include <xrpl/basics/Log.h>
25#include <xrpl/basics/SHAMapHash.h>
26#include <xrpl/basics/Slice.h>
27#include <xrpl/basics/ToString.h>
28#include <xrpl/basics/UptimeClock.h>
29#include <xrpl/basics/base64.h>
30#include <xrpl/basics/base_uint.h>
31#include <xrpl/basics/chrono.h>
32#include <xrpl/basics/random.h>
33#include <xrpl/basics/safe_cast.h>
34#include <xrpl/basics/strHex.h>
35#include <xrpl/beast/utility/Journal.h>
36#include <xrpl/beast/utility/Zero.h>
37#include <xrpl/beast/utility/instrumentation.h>
38#include <xrpl/consensus/Validations.h>
39#include <xrpl/core/HashRouter.h>
40#include <xrpl/core/Job.h>
41#include <xrpl/core/PerfLog.h>
42#include <xrpl/json/json_forwards.h>
43#include <xrpl/json/json_value.h>
44#include <xrpl/ledger/Ledger.h>
45#include <xrpl/peerfinder/Slot.h>
46#include <xrpl/peerfinder/Types.h>
47#include <xrpl/protocol/KeyType.h>
48#include <xrpl/protocol/LedgerHeader.h>
49#include <xrpl/protocol/Protocol.h>
50#include <xrpl/protocol/PublicKey.h>
51#include <xrpl/protocol/SField.h>
52#include <xrpl/protocol/STTx.h>
53#include <xrpl/protocol/Serializer.h>
54#include <xrpl/protocol/TxFlags.h>
55#include <xrpl/protocol/digest.h>
56#include <xrpl/protocol/jss.h>
57#include <xrpl/protocol/tokens.h>
58#include <xrpl/resource/Charge.h>
59#include <xrpl/resource/Consumer.h>
60#include <xrpl/resource/Disposition.h>
61#include <xrpl/resource/Fees.h>
62#include <xrpl/resource/Gossip.h>
63#include <xrpl/server/LoadFeeTrack.h>
64#include <xrpl/server/Manifest.h>
65#include <xrpl/server/NetworkOPs.h>
66#include <xrpl/shamap/SHAMap.h>
67#include <xrpl/shamap/SHAMapNodeID.h>
68#include <xrpl/tx/apply.h>
69
70#include <boost/algorithm/string/predicate.hpp>
71#include <boost/asio/bind_executor.hpp>
72#include <boost/asio/buffer.hpp>
73#include <boost/asio/completion_condition.hpp>
74#include <boost/asio/dispatch.hpp>
75#include <boost/asio/error.hpp>
76#include <boost/asio/strand.hpp>
77#include <boost/asio/write.hpp>
78#include <boost/beast/core/multi_buffer.hpp>
79#include <boost/beast/core/ostream.hpp>
80#include <boost/system/system_error.hpp>
81
82#include <google/protobuf/message.h>
83
84#include <xrpl.pb.h>
85
86#include <algorithm>
87#include <atomic>
88#include <chrono>
89#include <cstddef>
90#include <cstdint>
91#include <exception>
92#include <functional>
93#include <map>
94#include <memory>
95#include <mutex>
96#include <numeric>
97#include <optional>
98#include <shared_mutex>
99#include <sstream>
100#include <string>
101#include <string_view>
102#include <tuple>
103#include <utility>
104#include <vector>
105
106using namespace std::chrono_literals;
107
108namespace xrpl {
109
110namespace {
114constexpr std::chrono::milliseconds kPeerHighLatency{300};
115
119constexpr std::chrono::seconds kPeerTimerInterval{60};
120
121} // namespace
122
123// TODO: Remove this exclusion once unit tests are added after the hotfix
124// release.
125
127 Application& app,
128 id_t id,
130 http_request_type&& request,
131 PublicKey const& publicKey,
133 resource::Consumer consumer,
135 OverlayImpl& overlay)
136 : Child(overlay)
137 , app_(app)
138 , id_(id)
139 , fingerprint_(getFingerprint(slot->remoteEndpoint(), publicKey, to_string(id)))
141 , sink_(app_.getJournal("Peer"), prefix_)
142 , pSink_(app_.getJournal("Protocol"), prefix_)
143 , journal_(sink_)
145 , streamPtr_(std::move(streamPtr))
146 , socket_(streamPtr_->next_layer().socket())
148 , strand_(boost::asio::make_strand(socket_.get_executor()))
149 , timer_(waitable_timer{socket_.get_executor()})
150 , remoteAddress_(slot->remoteEndpoint())
151 , overlay_(overlay)
152 , inbound_(true)
153 , protocol_(std::move(protocol))
155 , trackingTime_(clock_type::now())
156 , publicKey_(publicKey)
157 , lastPingTime_(clock_type::now())
158 , creationTime_(clock_type::now())
159 , squelch_(app_.getJournal("Squelch"))
160 , usage_(consumer)
161 , fee_{.fee = resource::kFeeTrivialPeer, .context = ""}
162 , slot_(slot)
163 , request_(std::move(request))
167 ? Compressed::On
168 : Compressed::Off)
170 peerFeatureEnabled(headers_, kFeatureTxrr, app_.config().txReduceRelayEnable))
172 peerFeatureEnabled(headers_, kFeatureLedgerReplay, app_.config().ledgerReplay))
173 , ledgerReplayMsgHandler_(app, app.getLedgerReplayer())
174{
175 JLOG(journal_.info())
176 << "compression enabled " << (compressionEnabled_ == Compressed::On)
177 << " vp reduce-relay base squelch enabled "
178 << peerFeatureEnabled(headers_, kFeatureVprr, app_.config().vpReduceRelayBaseSquelchEnable)
179 << " tx reduce-relay enabled " << txReduceRelayEnabled_;
180}
181
183{
184 bool const inCluster{cluster()};
185
186 overlay_.deletePeer(id_);
187 overlay_.onPeerDeactivate(id_);
188 overlay_.peerFinder().onClosed(slot_);
189 overlay_.remove(slot_);
190
191 if (inCluster)
192 {
193 JLOG(journal_.warn()) << name() << " left cluster";
194 }
195}
196
197// Helper function to check for valid uint256 values in protobuf buffers
198static bool
200{
201 return pBuffStr.size() == uint256::size();
202}
203
204void
206{
207 dispatch(strand_, [self = shared_from_this()]() {
208 auto parseLedgerHash = [](std::string_view value) -> std::optional<uint256> {
209 if (uint256 ret; ret.parseHex(value))
210 return ret;
211
212 if (auto const s = base64Decode(value); s.size() == uint256::size())
213 return uint256::fromRaw(s);
214
215 return std::nullopt;
216 };
217
219 std::optional<uint256> previous;
220
221 if (auto const iter = self->headers_.find("Closed-Ledger"); iter != self->headers_.end())
222 {
223 closed = parseLedgerHash(iter->value());
224
225 if (!closed)
226 self->fail("Malformed handshake data (1)");
227 }
228
229 if (auto const iter = self->headers_.find("Previous-Ledger"); iter != self->headers_.end())
230 {
231 previous = parseLedgerHash(iter->value());
232
233 if (!previous)
234 self->fail("Malformed handshake data (2)");
235 }
236
237 if (previous && !closed)
238 self->fail("Malformed handshake data (3)");
239
240 {
241 std::scoped_lock const sl(self->recentLock_);
242 if (closed)
243 self->closedLedgerHash_ = *closed;
244 if (previous)
245 self->previousLedgerHash_ = *previous;
246 }
247
248 if (self->inbound_)
249 {
250 self->doAccept();
251 }
252 else
253 {
254 self->doProtocolStart();
255 }
256
257 // Anything else that needs to be done with the connection should be
258 // done in doProtocolStart
259 });
260}
261
262void
264{
265 dispatch(strand_, [self = shared_from_this()]() {
266 if (!self->socket_.is_open())
267 return;
268
269 self->close();
270 });
271}
272
273//------------------------------------------------------------------------------
274
275void
277{
278 dispatch(strand_, [self = shared_from_this(), m]() {
279 if (self->gracefulClose_)
280 return;
281 if (self->detaching_)
282 return;
283 if (!self->socket_.is_open())
284 return;
285
286 auto validator = m->getValidatorKey();
287 if (validator && !self->squelch_.expireSquelch(*validator))
288 {
289 self->overlay_.reportOutboundTraffic(
291 static_cast<int>(m->getBuffer(self->compressionEnabled_).size()));
292 return;
293 }
294
295 // report categorized outgoing traffic
296 self->overlay_.reportOutboundTraffic(
297 safeCast<TrafficCount::Category>(m->getCategory()),
298 static_cast<int>(m->getBuffer(self->compressionEnabled_).size()));
299
300 // report total outgoing traffic
301 self->overlay_.reportOutboundTraffic(
303 static_cast<int>(m->getBuffer(self->compressionEnabled_).size()));
304
305 auto sendqSize = self->sendQueue_.size();
306
307 if (sendqSize < tuning::kTargetSendQueue)
308 {
309 // To detect a peer that does not read from their
310 // side of the connection, we expect a peer to have
311 // a small sendq periodically
312 self->largeSendq_ = 0;
313 }
314 else if (
315 auto sink = self->journal_.debug();
316 sink && (sendqSize % tuning::kSendQueueLogFreq) == 0)
317 {
318 std::string const n = self->name();
319 sink << n << " sendq: " << sendqSize;
320 }
321
322 self->sendQueue_.push(m);
323
324 if (sendqSize != 0)
325 return;
326
327 boost::asio::async_write(
328 self->stream_,
329 boost::asio::buffer(self->sendQueue_.front()->getBuffer(self->compressionEnabled_)),
330 bind_executor(
331 self->strand_, [self](error_code const& ec, std::size_t bytesTransferred) {
332 self->onWriteMessage(ec, bytesTransferred);
333 }));
334 });
335}
336
337void
339{
340 dispatch(strand_, [self = shared_from_this()]() {
341 if (!self->txQueue_.empty())
342 {
343 protocol::TMHaveTransactions ht;
345 self->txQueue_, [&](auto const& hash) { ht.add_hashes(hash.data(), hash.size()); });
346 JLOG(self->pJournal_.trace()) << "sendTxQueue " << self->txQueue_.size();
347 self->txQueue_.clear();
348 self->send(std::make_shared<Message>(ht, protocol::mtHAVE_TRANSACTIONS));
349 }
350 });
351}
352
353void
355{
356 dispatch(strand_, [self = shared_from_this(), hash]() {
357 if (self->txQueue_.size() == reduce_relay::kMaxTxQueueSize)
358 {
359 JLOG(self->pJournal_.warn()) << "addTxQueue exceeds the cap";
360 self->sendTxQueue();
361 }
362
363 self->txQueue_.insert(hash);
364 JLOG(self->pJournal_.trace()) << "addTxQueue " << self->txQueue_.size();
365 });
366}
367
368void
370{
371 dispatch(strand_, [self = shared_from_this(), hash]() {
372 auto removed = self->txQueue_.erase(hash);
373 JLOG(self->pJournal_.trace()) << "removeTxQueue " << removed;
374 });
375}
376
377void
379{
380 dispatch(strand_, [self = shared_from_this(), fee, context]() {
381 if ((self->usage_.charge(fee, context) == resource::Disposition::Drop) &&
382 self->usage_.disconnect(self->pJournal_))
383 {
384 // Idempotent: only the first worker to observe Drop counts the
385 // metric and posts fail(). Without the guard, several queued
386 // workers can all see Drop before fail() lands on the strand,
387 // overcounting peerDisconnectsCharges_ and posting duplicate
388 // shutdowns. fail(std::string const&) self-posts to strand_
389 // when invoked off-strand.
390 bool expected = false;
391 if (self->chargeDisconnectFired_.compare_exchange_strong(
392 expected, true, std::memory_order_acq_rel))
393 {
394 self->overlay_.incPeerDisconnectCharges();
395 self->fail("charge: Resources");
396 }
397 }
398 });
399}
400
401//------------------------------------------------------------------------------
402
403bool
405{
406 auto const iter = headers_.find("Crawl");
407 if (iter == headers_.end())
408 return false;
409 return boost::iequals(iter->value(), "public");
410}
411
412bool
414{
415 return static_cast<bool>(app_.getCluster().member(publicKey_));
416}
417
420{
421 if (inbound_)
422 return headers_["User-Agent"];
423 return headers_["Server"];
424}
425
428{
430
431 ret[jss::public_key] = toBase58(TokenType::NodePublic, publicKey_);
432 ret[jss::address] = remoteAddress_.toString();
433
434 if (inbound_)
435 ret[jss::inbound] = true;
436
437 if (cluster())
438 {
439 ret[jss::cluster] = true;
440
441 if (auto const n = name(); !n.empty())
442 {
443 // Could move here if json::Value supported moving from a string
444 ret[jss::name] = n;
445 }
446 }
447
448 if (auto const d = domain(); !d.empty())
449 ret[jss::server_domain] = std::string{d};
450
451 if (auto const nid = headers_["Network-ID"]; !nid.empty())
452 ret[jss::network_id] = std::string{nid};
453
454 ret[jss::load] = usage_.balance();
455
456 if (auto const version = getVersion(); !version.empty())
457 ret[jss::version] = std::string{version};
458
459 ret[jss::protocol] = to_string(protocol_);
460
461 {
463 if (latency_)
464 ret[jss::latency] = static_cast<json::UInt>(latency_->count());
465 }
466
467 ret[jss::uptime] =
469
470 std::uint32_t minSeq = 0, maxSeq = 0;
471 ledgerRange(minSeq, maxSeq);
472
473 if ((minSeq != 0) || (maxSeq != 0))
474 ret[jss::complete_ledgers] = std::to_string(minSeq) + " - " + std::to_string(maxSeq);
475
476 switch (tracking_.load())
477 {
479 ret[jss::track] = "diverged";
480 break;
481
483 ret[jss::track] = "unknown";
484 break;
485
487 // Nothing to do here
488 break;
489 }
490
491 uint256 closedLedgerHash;
492 protocol::TMStatusChange lastStatus;
493 {
495 closedLedgerHash = closedLedgerHash_;
496 lastStatus = lastStatus_;
497 }
498
499 if (closedLedgerHash != beast::kZero)
500 ret[jss::ledger] = to_string(closedLedgerHash);
501
502 if (lastStatus.has_newstatus())
503 {
504 switch (lastStatus.newstatus())
505 {
506 case protocol::nsCONNECTING:
507 ret[jss::status] = "connecting";
508 break;
509
510 case protocol::nsCONNECTED:
511 ret[jss::status] = "connected";
512 break;
513
514 case protocol::nsMONITORING:
515 ret[jss::status] = "monitoring";
516 break;
517
518 case protocol::nsVALIDATING:
519 ret[jss::status] = "validating";
520 break;
521
522 case protocol::nsSHUTTING:
523 ret[jss::status] = "shutting";
524 break;
525
526 default:
527 JLOG(pJournal_.warn()) << "Unknown status: " << lastStatus.newstatus();
528 }
529 }
530
531 ret[jss::metrics] = json::Value(json::ValueType::Object);
532 ret[jss::metrics][jss::total_bytes_recv] = std::to_string(metrics_.recv.totalBytes());
533 ret[jss::metrics][jss::total_bytes_sent] = std::to_string(metrics_.sent.totalBytes());
534 ret[jss::metrics][jss::avg_bps_recv] = std::to_string(metrics_.recv.averageBytes());
535 ret[jss::metrics][jss::avg_bps_sent] = std::to_string(metrics_.sent.averageBytes());
536
537 return ret;
538}
539
540bool
542{
543 switch (f)
544 {
546 return protocol_ >= makeProtocol(2, 3);
549 }
550 return false;
551}
552
553//------------------------------------------------------------------------------
554
555bool
557{
558 {
560 if ((seq != 0) && (seq >= minLedger_) && (seq <= maxLedger_) &&
561 (tracking_.load() == Tracking::Converged))
562 return true;
564 return true;
565 }
566 return false;
567}
568
569void
571{
573
574 minSeq = minLedger_;
575 maxSeq = maxLedger_;
576}
577
578bool
579PeerImp::hasTxSet(uint256 const& hash) const
580{
582 return std::ranges::find(recentTxSets_, hash) != recentTxSets_.end();
583}
584
585void
587{
588 // Operations on closedLedgerHash_ and previousLedgerHash_ must be
589 // guarded by recentLock_.
592 closedLedgerHash_.zero();
593}
594
595bool
597{
599 return (tracking_ != Tracking::Diverged) && (uMin >= minLedger_) && (uMax <= maxLedger_);
600}
601
602//------------------------------------------------------------------------------
603
604void
606{
607 XRPL_ASSERT(strand_.running_in_this_thread(), "xrpl::PeerImp::close : strand in this thread");
608 if (!socket_.is_open())
609 return;
610
611 detaching_ = true; // DEPRECATED
612
613 cancelTimer();
614 error_code ec;
615 socket_.close(ec); // NOLINT(bugprone-unused-return-value)
616
617 overlay_.incPeerDisconnect();
618 JLOG((inbound_ ? journal_.debug() : journal_.info())) << "close: Closed";
619}
620
621void
623{
624 dispatch(strand_, [self = shared_from_this(), reason]() {
625 if (self->journal_.active(beast::Severity::Warning) && self->socket_.is_open())
626 {
627 std::string const n = self->name();
628 JLOG(self->journal_.warn()) << n << " failed: " << reason;
629 }
630 self->close();
631 });
632}
633
634void
636{
637 XRPL_ASSERT(strand_.running_in_this_thread(), "xrpl::PeerImp::fail : strand in this thread");
638 if (!socket_.is_open())
639 return;
640
641 JLOG(journal_.warn()) << name << ": " << ec.message();
642
643 close();
644}
645
646void
648{
649 XRPL_ASSERT(
650 strand_.running_in_this_thread(), "xrpl::PeerImp::gracefulClose : strand in this thread");
651 XRPL_ASSERT(socket_.is_open(), "xrpl::PeerImp::gracefulClose : socket is open");
652 XRPL_ASSERT(!gracefulClose_, "xrpl::PeerImp::gracefulClose : socket is not closing");
653 gracefulClose_ = true;
654 if (!sendQueue_.empty())
655 return;
656 setTimer();
657 stream_.async_shutdown(bind_executor(
658 strand_, [self = shared_from_this()](error_code const& ec) { self->onShutdown(ec); }));
659}
660
661void
663{
664 try
665 {
666 timer_.expires_after(kPeerTimerInterval);
667 }
668 catch (boost::system::system_error const& e)
669 {
670 JLOG(journal_.error()) << "setTimer: " << e.code();
671 return;
672 }
673 timer_.async_wait(bind_executor(
674 strand_, [self = shared_from_this()](error_code const& ec) { self->onTimer(ec); }));
675}
676
677// convenience for ignoring the error code
678void
680{
681 try
682 {
683 timer_.cancel();
684 }
685 catch (boost::system::system_error const&) // NOLINT(bugprone-empty-catch)
686 {
687 // ignored
688 }
689}
690
691//------------------------------------------------------------------------------
692
695{
697 ss << "[" << fingerprint << "] ";
698 return ss.str();
699}
700
701void
703{
704 if (!socket_.is_open())
705 return;
706
707 if (ec)
708 {
709 if (ec == boost::asio::error::operation_aborted)
710 return;
711
712 // This should never happen
713 JLOG(journal_.error()) << "onTimer: " << ec.message();
714 close();
715 return;
716 }
717
719 {
720 fail("Large send queue");
721 return;
722 }
723
724 if (auto const t = tracking_.load(); !inbound_ && t != Tracking::Converged)
725 {
726 clock_type::duration duration;
727
728 {
730 duration = clock_type::now() - trackingTime_;
731 }
732
733 if ((t == Tracking::Diverged && (duration > app_.config().maxDivergedTime)) ||
734 (t == Tracking::Unknown && (duration > app_.config().maxUnknownTime)))
735 {
736 overlay_.peerFinder().onFailure(slot_);
737 fail("Not useful");
738 return;
739 }
740 }
741
742 // Already waiting for PONG
743 if (lastPingSeq_)
744 {
745 fail("Ping Timeout");
746 return;
747 }
748
751
752 protocol::TMPing message;
753 message.set_type(protocol::TMPing::ptPING);
754 message.set_seq(*lastPingSeq_);
755
756 send(std::make_shared<Message>(message, protocol::mtPING));
757
758 setTimer();
759}
760
761void
763{
764 cancelTimer();
765
766 if (ec)
767 {
768 // - eof: the stream was cleanly closed
769 // - operation_aborted: an expired timer (slow shutdown)
770 // - stream_truncated: the tcp connection closed (no handshake) it could
771 // occur if a peer does not perform a graceful disconnect
772 // - broken_pipe: the peer is gone
773 bool const shouldLog =
774 (ec != boost::asio::error::eof && ec != boost::asio::error::operation_aborted &&
775 !ec.message().contains("application data after close notify"));
776
777 if (shouldLog)
778 {
779 JLOG(journal_.debug()) << "onShutdown: " << ec.message();
780 }
781 }
782
783 close();
784}
785
786//------------------------------------------------------------------------------
787void
789{
790 XRPL_ASSERT(readBuffer_.size() == 0, "xrpl::PeerImp::doAccept : empty read buffer");
791
792 auto const sharedValue = makeSharedValue(*streamPtr_, journal_);
793
794 // This shouldn't fail since we already computed
795 // the shared value successfully in OverlayImpl
796 if (!sharedValue)
797 {
798 fail("makeSharedValue: Unexpected failure");
799 return;
800 }
801
802 JLOG(journal_.info()) << "Protocol: " << to_string(protocol_);
803
804 if (auto member = app_.getCluster().member(publicKey_))
805 {
806 {
807 std::unique_lock const lock{nameMutex_};
808 name_ = *member;
809 }
810 JLOG(journal_.info()) << "Cluster name: " << *member;
811 }
812
813 overlay_.activate(shared_from_this());
814
815 // XXX Set timer: connection is in grace period to be useful.
816 // XXX Set timer: connection idle (idle may vary depending on connection
817 // type.)
818
820
821 boost::beast::ostream(*writeBuffer) << makeResponse(
822 !overlay_.peerFinder().config().peerPrivate,
823 request_,
824 overlay_.setup().publicIp,
825 remoteAddress_.address(),
826 *sharedValue,
827 overlay_.setup().networkID,
828 protocol_,
829 app_);
830
831 // Write the whole buffer and only start protocol when that's done.
832 boost::asio::async_write(
833 stream_,
834 writeBuffer->data(),
835 boost::asio::transfer_all(),
836 bind_executor(
837 strand_,
838 [this, writeBuffer, self = shared_from_this()](
839 error_code ec, std::size_t bytesTransferred) {
840 if (!socket_.is_open())
841 return;
842 if (ec)
843 {
844 if (ec == boost::asio::error::operation_aborted)
845 return;
846
847 fail("onWriteResponse", ec);
848 return;
849 }
850
851 if (writeBuffer->size() == bytesTransferred)
852 {
854 return;
855 }
856 fail("Failed to write header");
857 return;
858 }));
859}
860
863{
864 std::shared_lock const readLock{nameMutex_};
865 return name_;
866}
867
870{
871 return headers_["Server-Domain"];
872}
873
874//------------------------------------------------------------------------------
875
876// Protocol logic
877
878void
880{
882
883 // Send all the validator lists that have been loaded
884 if (inbound_)
885 {
886 app_.getValidators().forEachAvailable(
887 [&](std::string const& manifest,
888 std::uint32_t version,
890 PublicKey const& pubKey,
891 std::size_t maxSequence,
892 uint256 const& hash) {
894 *this,
895 0,
896 pubKey,
897 maxSequence,
898 version,
899 manifest,
900 blobInfos,
901 app_.getHashRouter(),
902 pJournal_);
903
904 // Don't send it next time.
905 app_.getHashRouter().addSuppressionPeer(hash, id_);
906 });
907 }
908
909 if (auto m = overlay_.getManifestsMessage())
910 send(m);
911
912 setTimer();
913}
914
915// Called repeatedly with protocol message data
916void
918{
919 if (!socket_.is_open())
920 return;
921
922 if (ec)
923 {
924 if (ec == boost::asio::error::operation_aborted)
925 return;
926
927 if (ec == boost::asio::error::eof)
928 {
929 JLOG(journal_.info()) << "EOF";
931 return;
932 }
933
934 fail("onReadMessage", ec);
935 return;
936 }
937
938 if (auto stream = journal_.trace())
939 {
940 stream << "onReadMessage: "
941 << (bytesTransferred > 0 ? to_string(bytesTransferred) + " bytes" : "");
942 }
943
944 metrics_.recv.addMessage(bytesTransferred);
945
946 readBuffer_.commit(bytesTransferred);
947
948 auto hint = tuning::kReadBufferBytes;
949
950 while (readBuffer_.size() > 0)
951 {
952 std::size_t bytesConsumed = 0;
953
954 using namespace std::chrono_literals;
955 std::tie(bytesConsumed, ec) = perf::measureDurationAndLog(
956 [&]() { return invokeProtocolMessage(readBuffer_.data(), *this, hint); },
957 "invokeProtocolMessage",
958 350ms,
959 journal_);
960
961 if (ec)
962 {
963 fail("onReadMessage", ec);
964 return;
965 }
966
967 if (!socket_.is_open())
968 return;
969
970 if (gracefulClose_)
971 return;
972
973 if (bytesConsumed == 0)
974 break;
975 readBuffer_.consume(bytesConsumed);
976 }
977
978 // Timeout on writes only
979 stream_.async_read_some(
981 bind_executor(
982 strand_,
983 [self = shared_from_this()](error_code const& ec, std::size_t bytesTransferred) {
984 self->onReadMessage(ec, bytesTransferred);
985 }));
986}
987
988void
990{
991 if (!socket_.is_open())
992 return;
993
994 if (ec)
995 {
996 if (ec == boost::asio::error::operation_aborted)
997 return;
998
999 fail("onWriteMessage", ec);
1000 return;
1001 }
1002 if (auto stream = journal_.trace())
1003 {
1004 stream << "onWriteMessage: "
1005 << (bytesTransferred > 0 ? to_string(bytesTransferred) + " bytes" : "");
1006 }
1007
1008 metrics_.sent.addMessage(bytesTransferred);
1009
1010 XRPL_ASSERT(!sendQueue_.empty(), "xrpl::PeerImp::onWriteMessage : non-empty send buffer");
1011 sendQueue_.pop();
1012 if (!sendQueue_.empty())
1013 {
1014 // Timeout on writes only
1015 boost::asio::async_write(
1016 stream_,
1017 boost::asio::buffer(sendQueue_.front()->getBuffer(compressionEnabled_)),
1018 bind_executor(
1019 strand_,
1020 [self = shared_from_this()](error_code const& ec, std::size_t bytesTransferred) {
1021 self->onWriteMessage(ec, bytesTransferred);
1022 }));
1023 return;
1024 }
1025
1026 if (gracefulClose_)
1027 {
1028 stream_.async_shutdown(bind_executor(
1029 strand_, [self = shared_from_this()](error_code const& ec) { self->onShutdown(ec); }));
1030 return;
1031 }
1032}
1033
1034//------------------------------------------------------------------------------
1035//
1036// ProtocolHandler
1037//
1038//------------------------------------------------------------------------------
1039
1040void
1042{
1043 // TODO
1044}
1045
1046void
1048 std::uint16_t type,
1050 std::size_t size,
1051 std::size_t uncompressedSize,
1052 bool isCompressed)
1053{
1054 auto const name = protocolMessageName(type);
1055 loadEvent_ = app_.getJobQueue().makeLoadEvent(JtPeer, name);
1056 fee_ = {.fee = resource::kFeeTrivialPeer, .context = name};
1057
1058 auto const category =
1059 TrafficCount::categorize(*m, static_cast<protocol::MessageType>(type), true);
1060
1061 // report total incoming traffic
1062 overlay_.reportInboundTraffic(TrafficCount::Category::Total, static_cast<int>(size));
1063
1064 // increase the traffic received for a specific category
1065 overlay_.reportInboundTraffic(category, static_cast<int>(size));
1066
1067 using namespace protocol;
1068 if ((type == MessageType::mtTRANSACTION || type == MessageType::mtHAVE_TRANSACTIONS ||
1069 type == MessageType::mtTRANSACTIONS ||
1070 // GET_OBJECTS
1072 // GET_LEDGER
1075 // LEDGER_DATA
1077 category == TrafficCount::Category::GlTscGet) &&
1078 (txReduceRelayEnabled() || app_.config().txReduceRelayMetrics))
1079 {
1080 overlay_.addTxMetrics(static_cast<MessageType>(type), static_cast<std::uint64_t>(size));
1081 }
1082 JLOG(journal_.trace()) << "onMessageBegin: " << type << " " << size << " " << uncompressedSize
1083 << " " << isCompressed;
1084}
1085
1086void
1092
1093void
1095{
1096 auto const s = m->list_size();
1097
1098 if (s == 0)
1099 {
1100 fee_.update(resource::kFeeUselessData, "empty");
1101 return;
1102 }
1103
1104 if (s > 100)
1105 fee_.update(resource::kFeeModerateBurdenPeer, "oversize");
1106
1107 // OverlayImpl::onManifests bounds the untrusted work and charges the fee
1108 // if the untrusted count exceeds the per-message cap; trusted manifests
1109 // are always processed and not counted against it.
1110 app_.getJobQueue().addJob(JtManifest, "RcvManifests", [this, that = shared_from_this(), m]() {
1111 overlay_.onManifests(m, that);
1112 });
1113}
1114
1115void
1117{
1118 if (m->type() == protocol::TMPing::ptPING)
1119 {
1120 // We have received a ping request, reply with a pong.
1121 fee_.update(resource::kFeeModerateBurdenPeer, "ping request");
1122 protocol::TMPing pong;
1123 pong.set_type(protocol::TMPing::ptPONG);
1124 if (m->has_seq())
1125 pong.set_seq(m->seq());
1126 send(std::make_shared<Message>(pong, protocol::mtPING));
1127 return;
1128 }
1129
1130 if (m->type() == protocol::TMPing::ptPONG && m->has_seq())
1131 {
1132 // Only reset the ping sequence if we actually received a
1133 // PONG with the correct cookie. That way, any peers which
1134 // respond with incorrect cookies will eventually time out.
1135 if (m->seq() == lastPingSeq_)
1136 {
1137 lastPingSeq_.reset();
1138
1139 // Update latency estimate
1140 auto const rtt =
1141 std::chrono::round<std::chrono::milliseconds>(clock_type::now() - lastPingTime_);
1142
1143 std::scoped_lock const sl(recentLock_);
1144
1145 if (latency_)
1146 {
1147 latency_ = (*latency_ * 7 + rtt) / 8;
1148 }
1149 else
1150 {
1151 latency_ = rtt;
1152 }
1153 }
1154
1155 return;
1156 }
1157}
1158
1159void
1161{
1162 // VFALCO NOTE I think we should drop the peer immediately
1163 if (!cluster())
1164 {
1165 fee_.update(resource::kFeeUselessData, "unknown cluster");
1166 return;
1167 }
1168
1169 for (int i = 0; i < m->clusternodes().size(); ++i)
1170 {
1171 protocol::TMClusterNode const& node = m->clusternodes(i);
1172
1174 if (node.has_nodename())
1175 name = node.nodename();
1176
1177 auto const publicKey = parseBase58<PublicKey>(TokenType::NodePublic, node.publickey());
1178
1179 // NIKB NOTE We should drop the peer immediately if
1180 // they send us a public key we can't parse
1181 if (publicKey)
1182 {
1183 auto const reportTime = NetClock::time_point{NetClock::duration{node.reporttime()}};
1184
1185 app_.getCluster().update(*publicKey, name, node.nodeload(), reportTime);
1186 }
1187 }
1188
1189 int const loadSources = m->loadsources().size();
1190 if (loadSources != 0)
1191 {
1192 resource::Gossip gossip;
1193 gossip.items.reserve(loadSources);
1194 for (int i = 0; i < m->loadsources().size(); ++i)
1195 {
1196 protocol::TMLoadSource const& node = m->loadsources(i);
1198 item.address = beast::ip::Endpoint::fromString(node.name());
1199 item.balance = node.cost();
1200 if (item.address != beast::ip::Endpoint())
1201 gossip.items.push_back(item);
1202 }
1203 overlay_.resourceManager().importConsumers(name(), gossip);
1204 }
1205
1206 // Calculate the cluster fee:
1207 auto const thresh = app_.getTimeKeeper().now() - 90s;
1208 std::uint32_t clusterFee = 0;
1209
1211 fees.reserve(app_.getCluster().size());
1212
1213 app_.getCluster().forEach([&fees, thresh](ClusterNode const& status) {
1214 if (status.getReportTime() >= thresh)
1215 fees.push_back(status.getLoadFee());
1216 });
1217
1218 if (!fees.empty())
1219 {
1220 auto const index = fees.size() / 2;
1221 std::nth_element(fees.begin(), fees.begin() + index, fees.end());
1222 clusterFee = fees[index];
1223 }
1224
1225 app_.getFeeTrack().setClusterFee(clusterFee);
1226}
1227
1228void
1230{
1231 // Don't allow endpoints from peers that are not known tracking or are
1232 // not using a version of the message that we support:
1233 if (tracking_.load() != Tracking::Converged || m->version() != 2)
1234 return;
1235
1236 // The number is arbitrary and doesn't have any real significance or
1237 // implication for the protocol.
1238 if (m->endpoints_v2().size() >= 1024)
1239 {
1240 fee_.update(resource::kFeeUselessData, "endpoints too large");
1241 return;
1242 }
1243
1245 endpoints.reserve(m->endpoints_v2().size());
1246
1247 auto malformed = 0;
1248 for (auto const& tm : m->endpoints_v2())
1249 {
1250 auto result = beast::ip::Endpoint::fromStringChecked(tm.endpoint());
1251
1252 if (!result)
1253 {
1254 JLOG(pJournal_.error())
1255 << "failed to parse incoming endpoint: {" << tm.endpoint() << "}";
1256 malformed++;
1257 continue;
1258 }
1259
1260 // If hops == 0, this Endpoint describes the peer we are connected
1261 // to -- in that case, we take the remote address seen on the
1262 // socket and store that in the ip::Endpoint. If this is the first
1263 // time, then we'll verify that their listener can receive incoming
1264 // by performing a connectivity test. if hops > 0, then we just
1265 // take the address/port we were given
1266 if (tm.hops() == 0)
1267 result = remoteAddress_.atPort(result->port());
1268
1269 endpoints.emplace_back(*result, tm.hops());
1270 }
1271
1272 // Charge the peer for each malformed endpoint. As there still may be
1273 // multiple valid endpoints we don't return early.
1274 if (malformed > 0)
1275 {
1276 fee_.update(
1277 resource::kFeeInvalidData * malformed,
1278 std::to_string(malformed) + " malformed endpoints");
1279 }
1280
1281 if (!endpoints.empty())
1282 overlay_.peerFinder().onEndpoints(slot_, endpoints);
1283}
1284
1285void
1290
1291void
1294 bool eraseTxQueue,
1295 bool batch)
1296{
1297 XRPL_ASSERT(eraseTxQueue != batch, ("xrpl::PeerImp::handleTransaction : valid inputs"));
1298 if (tracking_.load() == Tracking::Diverged)
1299 return;
1300
1301 if (app_.getOPs().isNeedNetworkLedger())
1302 {
1303 // If we've never been in synch, there's nothing we can do
1304 // with a transaction
1305 JLOG(pJournal_.debug()) << "Ignoring incoming transaction: Need network ledger";
1306 return;
1307 }
1308
1309 SerialIter sit(makeSlice(m->rawtransaction()));
1310
1311 try
1312 {
1313 auto stx = std::make_shared<STTx const>(sit);
1314 uint256 const txID = stx->getTransactionID();
1315
1316 // Charge strongly for attempting to relay a txn with tfInnerBatchTxn
1317 // LCOV_EXCL_START
1318 /*
1319 There is no need to check whether the featureBatchV1_1 amendment is
1320 enabled.
1321
1322 * If the `tfInnerBatchTxn` flag is set, and the amendment is
1323 enabled, then it's an invalid transaction because inner batch
1324 transactions should not be relayed.
1325 * If the `tfInnerBatchTxn` flag is set, and the amendment is *not*
1326 enabled, then the transaction is malformed because it's using an
1327 "unknown" flag. There's no need to waste the resources to send it
1328 to the transaction engine.
1329
1330 We don't normally check transaction validity at this level, but
1331 since we _need_ to check it when the amendment is enabled, we may as
1332 well drop it if the flag is set regardless.
1333 */
1334 if (stx->isFlag(tfInnerBatchTxn))
1335 {
1336 JLOG(pJournal_.warn()) << "Ignoring Network relayed Tx containing "
1337 "tfInnerBatchTxn (handleTransaction).";
1338 fee_.update(resource::kFeeModerateBurdenPeer, "inner batch txn");
1339 return;
1340 }
1341 // LCOV_EXCL_STOP
1342
1344 static constexpr std::chrono::seconds kTxInterval = 10s;
1345
1346 if (!app_.getHashRouter().shouldProcess(txID, id_, flags, kTxInterval))
1347 {
1348 // we have seen this transaction recently
1349 if (any(flags & HashRouterFlags::BAD))
1350 {
1351 fee_.update(resource::kFeeUselessData, "known bad");
1352 JLOG(pJournal_.debug()) << "Ignoring known bad tx " << txID;
1353 }
1354
1355 // Erase only if the server has seen this tx. If the server has not
1356 // seen this tx then the tx could not has been queued for this peer.
1357 else if (eraseTxQueue && txReduceRelayEnabled())
1358 {
1359 removeTxQueue(txID);
1360 }
1361
1362 overlay_.reportInboundTraffic(
1364
1365 return;
1366 }
1367
1368 JLOG(pJournal_.debug()) << "Got tx " << txID;
1369
1370 bool checkSignature = true;
1371 if (cluster())
1372 {
1373 if (!m->has_deferred() || !m->deferred())
1374 {
1375 // Skip local checks if a server we trust
1376 // put the transaction in its open ledger
1377 flags |= HashRouterFlags::TRUSTED;
1378 }
1379
1380 // for non-validator nodes only -- localPublicKey is set for
1381 // validators only
1382 if (!app_.getValidationPublicKey())
1383 {
1384 // For now, be paranoid and have each validator
1385 // check each transaction, regardless of source
1386 checkSignature = false;
1387 }
1388 }
1389
1390 if (app_.getLedgerMaster().getValidatedLedgerAge() > 4min)
1391 {
1392 JLOG(pJournal_.trace()) << "No new transactions until synchronized";
1393 }
1394 else if (app_.getJobQueue().getJobCount(JtTransaction) > app_.config().maxTransactions)
1395 {
1396 overlay_.incJqTransOverflow();
1397 JLOG(pJournal_.info()) << "Transaction queue is full";
1398 }
1399 else
1400 {
1401 app_.getJobQueue().addJob(
1403 "RcvCheckTx",
1405 flags,
1406 checkSignature,
1407 batch,
1408 stx]() {
1409 if (auto peer = weak.lock())
1410 peer->checkTransaction(flags, checkSignature, stx, batch);
1411 });
1412 }
1413 }
1414 catch (std::exception const& ex)
1415 {
1416 JLOG(pJournal_.warn()) << "Transaction invalid: " << strHex(m->rawtransaction())
1417 << ". Exception: " << ex.what();
1418 }
1419}
1420
1421void
1423{
1424 auto badData = [&](std::string const& msg) {
1425 fee_.update(resource::kFeeInvalidData, "get_ledger " + msg);
1426 JLOG(pJournal_.warn()) << "TMGetLedger: " << msg;
1427 };
1428 auto const itype{m->itype()};
1429
1430 // Verify ledger info type
1431 if (itype < protocol::liBASE || itype > protocol::liTS_CANDIDATE)
1432 {
1433 badData("Invalid ledger info type");
1434 return;
1435 }
1436
1437 auto const ltype = [&m]() -> std::optional<::protocol::TMLedgerType> {
1438 if (m->has_ltype())
1439 return m->ltype();
1440 return std::nullopt;
1441 }();
1442
1443 if (itype == protocol::liTS_CANDIDATE)
1444 {
1445 if (!m->has_ledgerhash())
1446 {
1447 badData("Invalid TX candidate set, missing TX set hash");
1448 return;
1449 }
1450 }
1451 else if (
1452 !m->has_ledgerhash() && !m->has_ledgerseq() && (!ltype || *ltype != protocol::ltCLOSED))
1453 {
1454 badData("Invalid request");
1455 return;
1456 }
1457
1458 // Verify ledger type
1459 if (ltype && (*ltype < protocol::ltACCEPTED || *ltype > protocol::ltCLOSED))
1460 {
1461 badData("Invalid ledger type");
1462 return;
1463 }
1464
1465 // Verify ledger hash
1466 if (m->has_ledgerhash() && !stringIsUInt256Sized(m->ledgerhash()))
1467 {
1468 badData("Invalid ledger hash");
1469 return;
1470 }
1471
1472 // Verify ledger sequence
1473 if (m->has_ledgerseq())
1474 {
1475 auto const ledgerSeq{m->ledgerseq()};
1476
1477 // Check if within a reasonable range
1478 using namespace std::chrono_literals;
1479 if (app_.getLedgerMaster().getValidatedLedgerAge() <= 10s &&
1480 ledgerSeq > app_.getLedgerMaster().getValidLedgerIndex() + 10)
1481 {
1482 badData("Invalid ledger sequence " + std::to_string(ledgerSeq));
1483 return;
1484 }
1485 }
1486
1487 // Verify ledger node counts. Full parsing of the node IDs is deferred to the job, so the I/O
1488 // thread is not burdened with SHAMapNodeID deserialization for every TMGetLedger message.
1489 if (itype != protocol::liBASE && m->nodeids_size() <= 0)
1490 {
1491 badData("Invalid ledger node IDs");
1492 return;
1493 }
1494
1495 // Verify query type
1496 if (m->has_querytype() && m->querytype() != protocol::qtINDIRECT)
1497 {
1498 badData("Invalid query type");
1499 return;
1500 }
1501
1502 // Verify query depth
1503 if (m->has_querydepth())
1504 {
1505 if (m->querydepth() > tuning::kMaxQueryDepth || itype == protocol::liBASE)
1506 {
1507 badData("Invalid query depth");
1508 return;
1509 }
1510 }
1511
1512 // Queue a job to process the request.
1514 app_.getJobQueue().addJob(JtLedgerReq, "RcvGetLedger", [weak, m, itype]() {
1515 auto peer = weak.lock();
1516 if (!peer)
1517 return;
1518
1520 bool tooManyNodeIds = false;
1521 if (itype != protocol::liBASE)
1522 {
1523 nodeIDs.reserve(std::min(m->nodeids_size(), tuning::kSoftMaxReplyNodes));
1524 for (auto const& nodeId : m->nodeids())
1525 {
1526 if (nodeIDs.size() >= tuning::kSoftMaxReplyNodes)
1527 {
1528 // The peer requested too many node IDs. Continue processing the received node
1529 // IDs up to the limit. If the request is legitimate then at least they will get
1530 // a response and won't have to resend these nodes in their next request.
1531 tooManyNodeIds = true;
1532 break;
1533 }
1534 auto parsed = deserializeSHAMapNodeID(nodeId);
1535 if (!parsed)
1536 {
1537 peer->charge(resource::kFeeInvalidData, "TMGetLedger: Invalid node ID");
1538 return;
1539 }
1540 nodeIDs.push_back(std::move(*parsed));
1541 }
1542 }
1543
1544 // These are two distinct infractions and are charged independently: requesting too many
1545 // node IDs is charged even for a relay response, while the base "get ledger request" charge
1546 // below is skipped for relay responses.
1547 if (tooManyNodeIds)
1548 {
1549 peer->charge(resource::kFeeModerateBurdenPeer, "TMGetLedger: too many node IDs");
1550
1551 // Truncate the request to what was actually parsed and charged for, so that if this
1552 // request ends up being relayed to another peer, we don't forward the oversized list.
1553 m->mutable_nodeids()->DeleteSubrange(
1554 static_cast<int>(nodeIDs.size()),
1555 m->nodeids_size() - static_cast<int>(nodeIDs.size()));
1556 }
1557 if (!m->has_requestcookie())
1558 {
1559 peer->charge(resource::kFeeModerateBurdenPeer, "TMGetLedger: get ledger request");
1560 }
1561
1562 peer->processLedgerRequest(m, std::move(nodeIDs));
1563 });
1564}
1565
1566void
1568{
1569 JLOG(pJournal_.trace()) << "onMessage, TMProofPathRequest";
1571 {
1572 fee_.update(resource::kFeeMalformedRequest, "proof_path_request disabled");
1573 return;
1574 }
1575
1576 fee_.update(resource::kFeeModerateBurdenPeer, "received a proof path request");
1578 app_.getJobQueue().addJob(JtReplayReq, "RcvProofPReq", [weak, m]() {
1579 if (auto peer = weak.lock())
1580 {
1581 auto reply = peer->ledgerReplayMsgHandler_.processProofPathRequest(m);
1582 if (reply.has_error())
1583 {
1584 if (reply.error() == protocol::TMReplyError::reBAD_REQUEST)
1585 {
1586 peer->charge(resource::kFeeMalformedRequest, "proof_path_request");
1587 }
1588 else
1589 {
1590 peer->charge(resource::kFeeRequestNoReply, "proof_path_request");
1591 }
1592 }
1593 else
1594 {
1595 peer->send(std::make_shared<Message>(reply, protocol::mtPROOF_PATH_RESPONSE));
1596 }
1597 }
1598 });
1599}
1600
1601void
1603{
1605 {
1606 fee_.update(resource::kFeeMalformedRequest, "proof_path_response disabled");
1607 return;
1608 }
1609
1610 switch (ledgerReplayMsgHandler_.processProofPathResponse(m))
1611 {
1613 break;
1615 fee_.update(resource::kFeeInvalidData, "proof_path_response");
1616 break;
1618 fee_.update(resource::kFeeMalformedData, "proof_path_response malformed");
1619 break;
1620 }
1621}
1622
1623void
1625{
1626 JLOG(pJournal_.trace()) << "onMessage, TMReplayDeltaRequest";
1628 {
1629 fee_.update(resource::kFeeMalformedRequest, "replay_delta_request disabled");
1630 return;
1631 }
1632
1635 app_.getJobQueue().addJob(JtReplayReq, "RcvReplDReq", [weak, m]() {
1636 if (auto peer = weak.lock())
1637 {
1638 auto reply = peer->ledgerReplayMsgHandler_.processReplayDeltaRequest(m);
1639 if (reply.has_error())
1640 {
1641 if (reply.error() == protocol::TMReplyError::reBAD_REQUEST)
1642 {
1643 peer->charge(resource::kFeeMalformedRequest, "replay_delta_request");
1644 }
1645 else
1646 {
1647 peer->charge(resource::kFeeRequestNoReply, "replay_delta_request");
1648 }
1649 }
1650 else
1651 {
1652 peer->send(std::make_shared<Message>(reply, protocol::mtREPLAY_DELTA_RESPONSE));
1653 }
1654 }
1655 });
1656}
1657
1658void
1660{
1662 {
1663 fee_.update(resource::kFeeMalformedRequest, "replay_delta_response disabled");
1664 return;
1665 }
1666
1667 switch (ledgerReplayMsgHandler_.processReplayDeltaResponse(m))
1668 {
1670 break;
1672 fee_.update(resource::kFeeInvalidData, "replay_delta_response");
1673 break;
1675 fee_.update(resource::kFeeMalformedData, "replay_delta_response malformed");
1676 break;
1677 }
1678}
1679
1680void
1682{
1683 auto badData = [&](std::string const& msg) {
1684 fee_.update(resource::kFeeInvalidData, msg);
1685 JLOG(pJournal_.warn()) << "TMLedgerData: " << msg;
1686 };
1687
1688 // Verify ledger hash
1689 if (!stringIsUInt256Sized(m->ledgerhash()))
1690 {
1691 badData("Invalid ledger hash");
1692 return;
1693 }
1694
1695 // Verify ledger sequence
1696 {
1697 auto const ledgerSeq{m->ledgerseq()};
1698 if (m->type() == protocol::liTS_CANDIDATE)
1699 {
1700 if (ledgerSeq != 0)
1701 {
1702 badData("Invalid ledger sequence " + std::to_string(ledgerSeq));
1703 return;
1704 }
1705 }
1706 else
1707 {
1708 // Check if within a reasonable range
1709 using namespace std::chrono_literals;
1710 if (app_.getLedgerMaster().getValidatedLedgerAge() <= 10s &&
1711 ledgerSeq > app_.getLedgerMaster().getValidLedgerIndex() + 10)
1712 {
1713 badData("Invalid ledger sequence " + std::to_string(ledgerSeq));
1714 return;
1715 }
1716 }
1717 }
1718
1719 // Verify ledger info type
1720 if (m->type() < protocol::liBASE || m->type() > protocol::liTS_CANDIDATE)
1721 {
1722 badData("Invalid ledger info type");
1723 return;
1724 }
1725
1726 // Verify reply error
1727 if (m->has_error() &&
1728 (m->error() < protocol::reNO_LEDGER || m->error() > protocol::reBAD_REQUEST))
1729 {
1730 badData("Invalid reply error");
1731 return;
1732 }
1733
1734 // Verify ledger nodes.
1735 if (m->nodes_size() <= 0 || m->nodes_size() > tuning::kHardMaxReplyNodes)
1736 {
1737 badData("Invalid Ledger/TXset nodes " + std::to_string(m->nodes_size()));
1738 return;
1739 }
1740
1741 // If there is a request cookie, attempt to relay the message.
1742 if (m->has_requestcookie())
1743 {
1744 if (auto peer = overlay_.findPeerByShortID(m->requestcookie()))
1745 {
1746 m->clear_requestcookie();
1747
1748 // If the original requester doesn't support the new depth-based format, rewrite any
1749 // nodes that use it back to the legacy nodeid format before relaying. Once all nodes
1750 // have upgraded, the old protocol version and this code can be removed. Make sure that
1751 // the format of the nodes is consistent - either all use the legacy format or the new
1752 // format, unless it is liBASE data in which case none of these fields should be set.
1753 auto const peerSupportsNodeDepth =
1754 peer->supportsFeature(ProtocolFeature::LedgerNodeDepth);
1755 enum class MessageType { Unknown, Base, Legacy, Depth };
1756 MessageType messageType = MessageType::Unknown;
1757 for (int i = 0; i < m->nodes_size(); ++i)
1758 {
1759 auto* ledgerNode = m->mutable_nodes(i);
1760
1761 // All nodes should have non-empty data. The field is required so we don't need to
1762 // check for presence first.
1763 if (ledgerNode->nodedata().empty())
1764 {
1765 badData(
1766 "Received node with empty data while relaying ledger data for " +
1767 to_string(uint256::fromRaw(m->ledgerhash())) + " to peer " +
1768 std::to_string(peer->id()));
1769 return;
1770 }
1771
1772 MessageType msgType = MessageType::Unknown;
1773 if (m->type() == protocol::liBASE)
1774 {
1775 if (ledgerNode->has_nodeid() || ledgerNode->has_id() || ledgerNode->has_depth())
1776 {
1777 badData(
1778 "Received liBASE message with node reference while relaying ledger "
1779 "data for " +
1780 to_string(uint256::fromRaw(m->ledgerhash())) + " to peer " +
1781 std::to_string(peer->id()));
1782 return;
1783 }
1784 msgType = MessageType::Base;
1785 }
1786 else
1787 {
1788 msgType = ledgerNode->has_nodeid() ? MessageType::Legacy : MessageType::Depth;
1789 }
1790 if (messageType != MessageType::Unknown && messageType != msgType)
1791 {
1792 badData(
1793 "Received mixed mode message while relaying ledger data for " +
1794 to_string(uint256::fromRaw(m->ledgerhash())) + " to peer " +
1795 std::to_string(peer->id()));
1796 return;
1797 }
1798 messageType = msgType;
1799
1800 if (peerSupportsNodeDepth || msgType != MessageType::Depth)
1801 continue;
1802
1803 SOMETIMES(
1804 !peerSupportsNodeDepth,
1805 "xrpl::PeerImp : relaying depth-format ledger data to pre-2.3 peer");
1806 switch (ledgerNode->reference_case())
1807 {
1808 case protocol::TMLedgerNode::kId: {
1809 // We can directly copy the `id` field, because it uses the same wire format
1810 // as the legacy `nodeid` field.
1811 REACHABLE("xrpl::PeerImp : relay downgrade id to nodeid");
1812 ledgerNode->set_nodeid(ledgerNode->id());
1813 ledgerNode->clear_id();
1814 break;
1815 }
1816 case protocol::TMLedgerNode::kDepth: {
1817 // We need to regenerate the node ID from the node data and depth.
1818 auto treeNode = getTreeNode(ledgerNode->nodedata());
1819 if (!treeNode)
1820 {
1821 badData(
1822 "Unable to get tree node while relaying ledger data for " +
1823 to_string(uint256::fromRaw(m->ledgerhash())) + " to peer " +
1824 std::to_string(peer->id()));
1825 return;
1826 }
1827
1828 auto const nodeID = getSHAMapNodeID(*ledgerNode, *treeNode);
1829 if (!nodeID)
1830 {
1831 badData(
1832 "Unable to get node ID while relaying ledger data for " +
1833 to_string(uint256::fromRaw(m->ledgerhash())) + " to peer " +
1834 std::to_string(peer->id()));
1835 return;
1836 }
1837
1838 REACHABLE("xrpl::PeerImp : relay downgrade depth to nodeid");
1839 ledgerNode->set_nodeid(nodeID->getRawString());
1840 ledgerNode->clear_depth();
1841 break;
1842 }
1843 default: {
1844 SOMETIMES(true, "xrpl::PeerImp : relay node has empty reference");
1845 badData(
1846 "Empty node reference while relaying ledger data for " +
1847 to_string(uint256::fromRaw(m->ledgerhash())) + " to peer " +
1848 std::to_string(peer->id()));
1849 return;
1850 }
1851 }
1852 }
1853
1854 peer->send(std::make_shared<Message>(*m, protocol::mtLEDGER_DATA));
1855 }
1856 else
1857 {
1858 JLOG(pJournal_.info()) << "Unable to route TX/ledger data reply";
1859 }
1860 return;
1861 }
1862
1863 uint256 const ledgerHash = uint256::fromRaw(m->ledgerhash());
1864
1865 // Otherwise check if received data for a candidate transaction set
1866 if (m->type() == protocol::liTS_CANDIDATE)
1867 {
1869 app_.getJobQueue().addJob(JtTxnData, "RcvPeerData", [weak, ledgerHash, m]() {
1870 if (auto peer = weak.lock())
1871 {
1872 peer->app_.getInboundTransactions().gotData(ledgerHash, peer, m);
1873 }
1874 });
1875 return;
1876 }
1877
1878 // Consume the message
1879 app_.getInboundLedgers().gotLedgerData(ledgerHash, shared_from_this(), m);
1880}
1881
1882void
1884{
1885 protocol::TMProposeSet const& set = *m;
1886
1887 auto const sig = makeSlice(set.signature());
1888
1889 // Preliminary check for the validity of the signature: A DER encoded
1890 // signature can't be longer than 72 bytes.
1891 if ((std::clamp<std::size_t>(sig.size(), 64, 72) != sig.size()) ||
1892 (publicKeyType(makeSlice(set.nodepubkey())) != KeyType::Secp256k1))
1893 {
1894 JLOG(pJournal_.warn()) << "Proposal: malformed";
1895 fee_.update(resource::kFeeInvalidSignature, " signature can't be longer than 72 bytes");
1896 return;
1897 }
1898
1899 if (!stringIsUInt256Sized(set.currenttxhash()) || !stringIsUInt256Sized(set.previousledger()))
1900 {
1901 JLOG(pJournal_.warn()) << "Proposal: malformed";
1902 fee_.update(resource::kFeeMalformedRequest, "bad hashes");
1903 return;
1904 }
1905
1906 // RH TODO: when isTrusted = false we should probably also cache a key
1907 // suppression for 30 seconds to avoid doing a relatively expensive lookup
1908 // every time a spam packet is received
1909 PublicKey const publicKey{makeSlice(set.nodepubkey())};
1910 auto const isTrusted = app_.getValidators().trusted(publicKey);
1911
1912 // If the operator has specified that untrusted proposals be dropped then
1913 // this happens here I.e. before further wasting CPU verifying the signature
1914 // of an untrusted key
1915 if (!isTrusted)
1916 {
1917 // report untrusted proposal messages
1918 overlay_.reportInboundTraffic(
1920
1921 if (app_.config().relayUntrustedProposals == -1)
1922 return;
1923 }
1924
1925 uint256 const proposeHash = uint256::fromRaw(set.currenttxhash());
1926 uint256 const prevLedger = uint256::fromRaw(set.previousledger());
1927
1928 NetClock::time_point const closeTime{NetClock::duration{set.closetime()}};
1929
1930 uint256 const suppression = proposalUniqueId(
1931 proposeHash, prevLedger, set.proposeseq(), closeTime, publicKey.slice(), sig);
1932
1933 if (auto [added, relayed] = app_.getHashRouter().addSuppressionPeerWithStatus(suppression, id_);
1934 !added)
1935 {
1936 // Count unique messages (Slots has it's own 'HashRouter'), which a peer
1937 // receives within IDLED seconds since the message has been relayed.
1938 if (relayed && (stopwatch().now() - *relayed) < reduce_relay::kIdled)
1939 overlay_.updateSlotAndSquelch(suppression, publicKey, id_, protocol::mtPROPOSE_LEDGER);
1940
1941 // report duplicate proposal messages
1942 overlay_.reportInboundTraffic(
1944
1945 JLOG(pJournal_.trace()) << "Proposal: duplicate";
1946
1947 return;
1948 }
1949
1950 if (!isTrusted)
1951 {
1952 if (tracking_.load() == Tracking::Diverged)
1953 {
1954 JLOG(pJournal_.debug()) << "Proposal: Dropping untrusted (peer divergence)";
1955 return;
1956 }
1957
1958 if (!cluster() && app_.getFeeTrack().isLoadedLocal())
1959 {
1960 JLOG(pJournal_.debug()) << "Proposal: Dropping untrusted (load)";
1961 return;
1962 }
1963 }
1964
1965 JLOG(pJournal_.trace()) << "Proposal: " << (isTrusted ? "trusted" : "untrusted");
1966
1967 auto proposal = RCLCxPeerPos(
1968 publicKey,
1969 sig,
1970 suppression,
1972 prevLedger,
1973 set.proposeseq(),
1974 proposeHash,
1975 closeTime,
1976 app_.getTimeKeeper().closeTime(),
1977 calcNodeID(app_.getValidatorManifests().getMasterKey(publicKey))});
1978
1980 app_.getJobQueue().addJob(
1981 isTrusted ? JtProposalT : JtProposalUt, "checkPropose", [weak, isTrusted, m, proposal]() {
1982 if (auto peer = weak.lock())
1983 peer->checkPropose(isTrusted, m, proposal);
1984 });
1985}
1986
1987void
1989{
1990 JLOG(pJournal_.trace()) << "Status: Change";
1991
1992 if (!m->has_networktime())
1993 m->set_networktime(app_.getTimeKeeper().now().time_since_epoch().count());
1994
1995 {
1996 std::scoped_lock const sl(recentLock_);
1997 if (!lastStatus_.has_newstatus() || m->has_newstatus())
1998 {
1999 lastStatus_ = *m;
2000 }
2001 else
2002 {
2003 // preserve old status
2004 protocol::NodeStatus const status = lastStatus_.newstatus();
2005 lastStatus_ = *m;
2006 m->set_newstatus(status);
2007 }
2008 }
2009
2010 if (m->newevent() == protocol::neLOST_SYNC)
2011 {
2012 bool outOfSync{false};
2013 {
2014 // Operations on closedLedgerHash_ and previousLedgerHash_ must be
2015 // guarded by recentLock_.
2016 std::scoped_lock const sl(recentLock_);
2017 if (!closedLedgerHash_.isZero())
2018 {
2019 outOfSync = true;
2020 closedLedgerHash_.zero();
2021 }
2022 previousLedgerHash_.zero();
2023 }
2024 if (outOfSync)
2025 {
2026 JLOG(pJournal_.debug()) << "Status: Out of sync";
2027 }
2028 return;
2029 }
2030
2031 {
2032 uint256 closedLedgerHash{};
2033 bool const peerChangedLedgers{m->has_ledgerhash() && stringIsUInt256Sized(m->ledgerhash())};
2034
2035 {
2036 // Operations on closedLedgerHash_ and previousLedgerHash_ must be
2037 // guarded by recentLock_.
2038 std::scoped_lock const sl(recentLock_);
2039 if (peerChangedLedgers)
2040 {
2041 closedLedgerHash_ = m->ledgerhash();
2042 closedLedgerHash = closedLedgerHash_;
2043 addLedger(closedLedgerHash, sl);
2044 }
2045 else
2046 {
2047 closedLedgerHash_.zero();
2048 }
2049
2050 if (m->has_ledgerhashprevious() && stringIsUInt256Sized(m->ledgerhashprevious()))
2051 {
2052 previousLedgerHash_ = m->ledgerhashprevious();
2054 }
2055 else
2056 {
2057 previousLedgerHash_.zero();
2058 }
2059 }
2060 if (peerChangedLedgers)
2061 {
2062 JLOG(pJournal_.debug()) << "LCL is " << closedLedgerHash;
2063 }
2064 else
2065 {
2066 JLOG(pJournal_.debug()) << "Status: No ledger";
2067 }
2068 }
2069
2070 if (m->has_firstseq() && m->has_lastseq())
2071 {
2072 std::scoped_lock const sl(recentLock_);
2073
2074 minLedger_ = m->firstseq();
2075 maxLedger_ = m->lastseq();
2076
2077 if ((maxLedger_ < minLedger_) || (minLedger_ == 0) || (maxLedger_ == 0))
2078 minLedger_ = maxLedger_ = 0;
2079 }
2080
2081 if (m->has_ledgerseq() && app_.getLedgerMaster().getValidatedLedgerAge() < 2min)
2082 {
2083 checkTracking(m->ledgerseq(), app_.getLedgerMaster().getValidLedgerIndex());
2084 }
2085
2086 app_.getOPs().pubPeerStatus([m, this]() -> json::Value {
2088
2089 if (m->has_newstatus())
2090 {
2091 switch (m->newstatus())
2092 {
2093 case protocol::nsCONNECTING:
2094 j[jss::status] = "CONNECTING";
2095 break;
2096 case protocol::nsCONNECTED:
2097 j[jss::status] = "CONNECTED";
2098 break;
2099 case protocol::nsMONITORING:
2100 j[jss::status] = "MONITORING";
2101 break;
2102 case protocol::nsVALIDATING:
2103 j[jss::status] = "VALIDATING";
2104 break;
2105 case protocol::nsSHUTTING:
2106 j[jss::status] = "SHUTTING";
2107 break;
2108 }
2109 }
2110
2111 if (m->has_newevent())
2112 {
2113 switch (m->newevent())
2114 {
2115 case protocol::neCLOSING_LEDGER:
2116 j[jss::action] = "CLOSING_LEDGER";
2117 break;
2118 case protocol::neACCEPTED_LEDGER:
2119 j[jss::action] = "ACCEPTED_LEDGER";
2120 break;
2121 case protocol::neSWITCHED_LEDGER:
2122 j[jss::action] = "SWITCHED_LEDGER";
2123 break;
2124 case protocol::neLOST_SYNC:
2125 j[jss::action] = "LOST_SYNC";
2126 break;
2127 }
2128 }
2129
2130 if (m->has_ledgerseq())
2131 {
2132 j[jss::ledger_index] = m->ledgerseq();
2133 }
2134
2135 if (m->has_ledgerhash())
2136 {
2137 uint256 closedLedgerHash{};
2138 {
2139 std::scoped_lock const sl(recentLock_);
2140 closedLedgerHash = closedLedgerHash_;
2141 }
2142 j[jss::ledger_hash] = to_string(closedLedgerHash);
2143 }
2144
2145 if (m->has_networktime())
2146 {
2147 j[jss::date] = json::UInt(m->networktime());
2148 }
2149
2150 if (m->has_firstseq() && m->has_lastseq())
2151 {
2152 j[jss::ledger_index_min] = json::UInt(m->firstseq());
2153 j[jss::ledger_index_max] = json::UInt(m->lastseq());
2154 }
2155
2156 return j;
2157 });
2158}
2159
2160void
2162{
2163 std::uint32_t serverSeq = 0;
2164 {
2165 // Extract the sequence number of the highest
2166 // ledger this peer has
2167 std::scoped_lock const sl(recentLock_);
2168
2169 serverSeq = maxLedger_;
2170 }
2171 if (serverSeq != 0)
2172 {
2173 // Compare the peer's ledger sequence to the
2174 // sequence of a recently-validated ledger
2175 checkTracking(serverSeq, validationSeq);
2176 }
2177}
2178
2179void
2181{
2182 std::uint32_t const diff = std::max(seq1, seq2) - std::min(seq1, seq2);
2183
2185 {
2186 // The peer's ledger sequence is close to the validation's
2188 }
2189
2190 if ((diff > tuning::kDivergedLedgerLimit) && (tracking_.load() != Tracking::Diverged))
2191 {
2192 // The peer's ledger sequence is way off the validation's
2193 std::scoped_lock const sl(recentLock_);
2194
2197 }
2198}
2199
2200void
2202{
2203 if (!stringIsUInt256Sized(m->hash()))
2204 {
2205 fee_.update(resource::kFeeMalformedRequest, "bad hash");
2206 return;
2207 }
2208
2209 uint256 const hash = uint256::fromRaw(m->hash());
2210
2211 if (m->status() == protocol::tsHAVE)
2212 {
2213 std::scoped_lock const sl(recentLock_);
2214
2215 if (std::ranges::find(recentTxSets_, hash) != recentTxSets_.end())
2216 {
2217 fee_.update(resource::kFeeUselessData, "duplicate (tsHAVE)");
2218 return;
2219 }
2220
2221 recentTxSets_.push_back(hash);
2222 }
2223}
2224
2225void
2227 std::string const& messageType,
2228 std::string const& manifest,
2229 std::uint32_t version,
2230 std::vector<ValidatorBlobInfo> const& blobs)
2231{
2232 // If there are no blobs, the message is malformed (possibly because of
2233 // ValidatorList class rules), so charge accordingly and skip processing.
2234 if (blobs.empty())
2235 {
2236 JLOG(pJournal_.warn()) << "Ignored malformed " << messageType;
2237 // This shouldn't ever happen with a well-behaved peer
2238 fee_.update(resource::kFeeHeavyBurdenPeer, "no blobs");
2239 return;
2240 }
2241
2242 auto const hash = sha512Half(manifest, blobs, version);
2243
2244 JLOG(pJournal_.debug()) << "Received " << messageType;
2245
2246 if (!app_.getHashRouter().addSuppressionPeer(hash, id_))
2247 {
2248 JLOG(pJournal_.debug()) << messageType << ": received duplicate " << messageType;
2249 // Charging this fee here won't hurt the peer in the normal
2250 // course of operation (ie. refresh every 5 minutes), but
2251 // will add up if the peer is misbehaving.
2252 fee_.update(resource::kFeeUselessData, "duplicate");
2253 return;
2254 }
2255
2256 auto const applyResult = app_.getValidators().applyListsAndBroadcast(
2257 manifest,
2258 version,
2259 blobs,
2260 remoteAddress_.toString(),
2261 hash,
2262 app_.getOverlay(),
2263 app_.getHashRouter(),
2264 app_.getOPs());
2265
2266 JLOG(pJournal_.debug()) << "Processed " << messageType << " version " << version << " from "
2267 << (applyResult.publisherKey ? strHex(*applyResult.publisherKey)
2268 : "unknown or invalid publisher")
2269 << " with best result " << to_string(applyResult.bestDisposition());
2270
2271 // Act based on the best result
2272 switch (applyResult.bestDisposition())
2273 {
2274 // New list
2276 // Newest list is expired, and that needs to be broadcast, too
2278 // Future list
2281
2282 XRPL_ASSERT(
2283 applyResult.publisherKey,
2284 "xrpl::PeerImp::onValidatorListMessage : publisher key is "
2285 "set");
2286 // NOLINTNEXTLINE(bugprone-unchecked-optional-access) assert above
2287 auto const& pubKey = *applyResult.publisherKey;
2288#ifndef NDEBUG
2289 if (auto const iter = publisherListSequences_.find(pubKey);
2290 iter != publisherListSequences_.end())
2291 {
2292 XRPL_ASSERT(
2293 iter->second < applyResult.sequence,
2294 "xrpl::PeerImp::onValidatorListMessage : lower sequence");
2295 }
2296#endif
2297 publisherListSequences_[pubKey] = applyResult.sequence;
2298 }
2299 break;
2300 // NOLINTNEXTLINE(bugprone-branch-clone): identical to the next branch only in Release
2303#ifndef NDEBUG
2304 {
2306 XRPL_ASSERT(
2307 applyResult.sequence && applyResult.publisherKey,
2308 "xrpl::PeerImp::onValidatorListMessage : nonzero sequence "
2309 "and set publisher key");
2310 XRPL_ASSERT(
2311 publisherListSequences_[*applyResult.publisherKey] <= applyResult.sequence,
2312 "xrpl::PeerImp::onValidatorListMessage : maximum sequence");
2313 }
2314#endif // !NDEBUG
2315
2316 break;
2321 break;
2322 // LCOV_EXCL_START
2323 default:
2324 UNREACHABLE(
2325 "xrpl::PeerImp::onValidatorListMessage : invalid best list "
2326 "disposition");
2327 // LCOV_EXCL_STOP
2328 }
2329
2330 // Charge based on the worst result
2331 switch (applyResult.worstDisposition())
2332 {
2336 // No charges for good data
2337 break;
2340 // Charging this fee here won't hurt the peer in the normal
2341 // course of operation (ie. refresh every 5 minutes), but
2342 // will add up if the peer is misbehaving.
2343 fee_.update(resource::kFeeUselessData, " duplicate (same_sequence or known_sequence)");
2344 break;
2346 // There are very few good reasons for a peer to send an
2347 // old list, particularly more than once.
2348 fee_.update(resource::kFeeInvalidData, "expired");
2349 break;
2351 // Charging this fee here won't hurt the peer in the normal
2352 // course of operation (ie. refresh every 5 minutes), but
2353 // will add up if the peer is misbehaving.
2354 fee_.update(resource::kFeeUselessData, "untrusted");
2355 break;
2357 // This shouldn't ever happen with a well-behaved peer
2358 fee_.update(resource::kFeeInvalidSignature, "invalid list disposition");
2359 break;
2361 // During a version transition, this may be legitimate.
2362 // If it happens frequently, that's probably bad.
2363 fee_.update(resource::kFeeInvalidData, "version");
2364 break;
2365 // LCOV_EXCL_START
2366 default:
2367 UNREACHABLE(
2368 "xrpl::PeerImp::onValidatorListMessage : invalid worst list "
2369 "disposition");
2370 // LCOV_EXCL_STOP
2371 }
2372
2373 // Log based on all the results.
2374 for (auto const& [disp, count] : applyResult.dispositions)
2375 {
2376 switch (disp)
2377 {
2378 // New list
2380 JLOG(pJournal_.debug()) << "Applied " << count << " new " << messageType;
2381 break;
2382 // Newest list is expired, and that needs to be broadcast, too
2384 JLOG(pJournal_.debug()) << "Applied " << count << " expired " << messageType;
2385 break;
2386 // Future list
2388 JLOG(pJournal_.debug()) << "Processed " << count << " future " << messageType;
2389 break;
2391 JLOG(pJournal_.warn())
2392 << "Ignored " << count << " " << messageType << "(s) with current sequence";
2393 break;
2395 JLOG(pJournal_.warn())
2396 << "Ignored " << count << " " << messageType << "(s) with future sequence";
2397 break;
2399 JLOG(pJournal_.warn()) << "Ignored " << count << "stale " << messageType;
2400 break;
2402 JLOG(pJournal_.warn()) << "Ignored " << count << " untrusted " << messageType;
2403 break;
2405 JLOG(pJournal_.warn())
2406 << "Ignored " << count << "unsupported version " << messageType;
2407 break;
2409 JLOG(pJournal_.warn()) << "Ignored " << count << "invalid " << messageType;
2410 break;
2411 // LCOV_EXCL_START
2412 default:
2413 UNREACHABLE(
2414 "xrpl::PeerImp::onValidatorListMessage : invalid list "
2415 "disposition");
2416 // LCOV_EXCL_STOP
2417 }
2418 }
2419}
2420
2421void
2423{
2424 try
2425 {
2426 if (m->version() < 2)
2427 {
2428 JLOG(pJournal_.debug())
2429 << "ValidatorListCollection: received invalid validator list "
2430 "version "
2431 << m->version() << " from peer using protocol version " << to_string(protocol_);
2432 fee_.update(resource::kFeeInvalidData, "wrong version");
2433 return;
2434 }
2436 "ValidatorListCollection", m->manifest(), m->version(), ValidatorList::parseBlobs(*m));
2437 }
2438 catch (std::exception const& e)
2439 {
2440 JLOG(pJournal_.warn()) << "ValidatorListCollection: Exception, " << e.what();
2441 using namespace std::string_literals;
2442 fee_.update(resource::kFeeInvalidData, e.what());
2443 }
2444}
2445
2446void
2448{
2449 if (m->validation().size() < 50)
2450 {
2451 JLOG(pJournal_.warn()) << "Validation: Too small";
2452 fee_.update(resource::kFeeMalformedRequest, "too small");
2453 return;
2454 }
2455
2456 try
2457 {
2458 auto const closeTime = app_.getTimeKeeper().closeTime();
2459
2461 {
2462 SerialIter sit(makeSlice(m->validation()));
2463 try
2464 {
2466 std::ref(sit),
2467 [this](PublicKey const& pk) {
2468 return calcNodeID(app_.getValidatorManifests().getMasterKey(pk));
2469 },
2471 .checkSignature = false, .requireCanonicalOrder = true});
2472 }
2473 catch (std::exception const& e)
2474 {
2475 JLOG(pJournal_.warn()) << "Validation: Exception, " << e.what();
2476 fee_.update(resource::kFeeInvalidData, e.what());
2477 return;
2478 }
2479 val->setSeen(closeTime);
2480 }
2481
2482 if (!isCurrent(
2483 app_.getValidations().parms(),
2484 app_.getTimeKeeper().closeTime(),
2485 val->getSignTime(),
2486 val->getSeenTime()))
2487 {
2488 JLOG(pJournal_.trace()) << "Validation: Not current";
2489 fee_.update(resource::kFeeUselessData, "not current");
2490 return;
2491 }
2492
2493 // RH TODO: when isTrusted = false we should probably also cache a key
2494 // suppression for 30 seconds to avoid doing a relatively expensive
2495 // lookup every time a spam packet is received
2496 auto const isTrusted = app_.getValidators().trusted(val->getSignerPublic());
2497
2498 // If the operator has specified that untrusted validations be
2499 // dropped then this happens here I.e. before further wasting CPU
2500 // verifying the signature of an untrusted key
2501 if (!isTrusted)
2502 {
2503 // increase untrusted validations received
2504 overlay_.reportInboundTraffic(
2506
2507 if (app_.config().relayUntrustedValidations == -1)
2508 return;
2509 }
2510
2511 auto key = sha512Half(makeSlice(m->validation()));
2512
2513 auto [added, relayed] = app_.getHashRouter().addSuppressionPeerWithStatus(key, id_);
2514
2515 if (!added)
2516 {
2517 // Count unique messages (Slots has it's own 'HashRouter'), which a
2518 // peer receives within IDLED seconds since the message has been
2519 // relayed.
2520 if (relayed && (stopwatch().now() - *relayed) < reduce_relay::kIdled)
2521 {
2522 overlay_.updateSlotAndSquelch(
2523 key, val->getSignerPublic(), id_, protocol::mtVALIDATION);
2524 }
2525
2526 // increase duplicate validations received
2527 overlay_.reportInboundTraffic(
2529
2530 JLOG(pJournal_.trace()) << "Validation: duplicate";
2531 return;
2532 }
2533
2534 if (!isTrusted && (tracking_.load() == Tracking::Diverged))
2535 {
2536 JLOG(pJournal_.debug()) << "Dropping untrusted validation from diverged peer";
2537 }
2538 else if (isTrusted || !app_.getFeeTrack().isLoadedLocal())
2539 {
2540 std::string const name = isTrusted ? "ChkTrust" : "ChkUntrust";
2541
2543 app_.getJobQueue().addJob(
2544 isTrusted ? JtValidationT : JtValidationUt, name, [weak, val, m, key]() {
2545 if (auto peer = weak.lock())
2546 peer->checkValidation(val, key, m);
2547 });
2548 }
2549 else
2550 {
2551 JLOG(pJournal_.debug()) << "Dropping untrusted validation for load";
2552 }
2553 }
2554 catch (std::exception const& e)
2555 {
2556 JLOG(pJournal_.warn()) << "Exception processing validation: " << e.what();
2557 using namespace std::string_literals;
2559 }
2560}
2561
2562void
2564{
2565 protocol::TMGetObjectByHash const& packet = *m;
2566
2567 JLOG(pJournal_.trace()) << "received TMGetObjectByHash " << packet.type() << " "
2568 << packet.objects_size();
2569
2570 if (packet.query())
2571 {
2572 // this is a query
2573 if (sendQueue_.size() >= tuning::kDropSendQueue)
2574 {
2575 JLOG(pJournal_.debug()) << "GetObject: Large send queue";
2576 return;
2577 }
2578
2579 if (packet.type() == protocol::TMGetObjectByHash::otFETCH_PACK)
2580 {
2581 doFetchPack(m);
2582 return;
2583 }
2584
2585 if (packet.type() == protocol::TMGetObjectByHash::otTRANSACTIONS)
2586 {
2587 if (!txReduceRelayEnabled())
2588 {
2589 JLOG(pJournal_.error()) << "TMGetObjectByHash: tx reduce-relay is disabled";
2590 fee_.update(resource::kFeeMalformedRequest, "disabled");
2591 return;
2592 }
2593
2595 app_.getJobQueue().addJob(JtRequestedTxn, "DoTxs", [weak, m]() {
2596 if (auto peer = weak.lock())
2597 peer->doTransactions(m);
2598 });
2599 return;
2600 }
2601
2602 if (packet.has_ledgerhash())
2603 {
2604 if (!stringIsUInt256Sized(packet.ledgerhash()))
2605 {
2606 JLOG(pJournal_.debug()) << "GetObj: malformed ledgerhash from peer " << id_;
2607 fee_.update(resource::kFeeMalformedRequest, "get object ledger hash");
2608 return;
2609 }
2610 }
2611 // Reject oversized requests before touching the NodeStore.
2612 // The legitimate upper bound (InboundLedger::getNeededHashes())
2613 // is 8 hashes; anything beyond kHardMaxReplyNodes is non-conforming.
2614 if (packet.objects_size() > tuning::kHardMaxReplyNodes)
2615 {
2616 JLOG(pJournal_.warn())
2617 << "GetObj: oversized request from peer " << id_ << " (" << packet.objects_size()
2618 << " > " << tuning::kHardMaxReplyNodes << ")";
2619 fee_.update(resource::kFeeInvalidData, "oversized get object request");
2620 return;
2621 }
2622
2623 // Dispatch heavy synchronous NodeStore lookups off the peer's
2624 // I/O strand and onto the bounded job queue, mirroring the pattern
2625 // used by processLedgerRequest.
2627 bool const queued = app_.getJobQueue().addJob(JtLedgerReq, "RcvGetObjByHash", [weak, m]() {
2628 auto peer = weak.lock();
2629 if (!peer)
2630 return;
2631 try
2632 {
2633 peer->processGetObjectByHash(m);
2634 }
2635 catch (std::exception const& e)
2636 {
2637 // Surface backend failures (NodeStore I/O, allocation)
2638 // back through the resource model so a misbehaving peer
2639 // is still accountable rather than silently dropped.
2640 JLOG(peer->pJournal_.warn()) << "GetObj: handler threw: " << e.what();
2641 peer->charge(resource::kFeeRequestNoReply, "get object handler exception");
2642 }
2643 });
2644 if (!queued)
2645 {
2646 // The JobQueue is no longer accepting new work (typically
2647 // because it is shutting down / has been joined).
2648 JLOG(pJournal_.warn()) << "GetObj: job queue refused request from peer " << id_;
2649 return;
2650 }
2651
2652 // Admission-time charge: a peer that floods enqueues would
2653 // otherwise be billed only the trivial onMessageEnd fee per
2654 // message until the JobQueue catches up, re-creating an
2655 // uncharged DoS window. Charge the base burden up-front (after
2656 // a successful enqueue); the per-lookup differential is added
2657 // in the worker.
2658 fee_.update(resource::kFeeModerateBurdenPeer, "received a get object by hash request");
2659 }
2660 else
2661 {
2662 // this is a reply
2663 std::uint32_t pLSeq = 0;
2664 bool pLDo = true;
2665 bool progress = false;
2666
2667 for (int i = 0; i < packet.objects_size(); ++i)
2668 {
2669 protocol::TMIndexedObject const& obj = packet.objects(i);
2670
2671 if (obj.has_hash() && stringIsUInt256Sized(obj.hash()))
2672 {
2673 if (obj.has_ledgerseq())
2674 {
2675 if (obj.ledgerseq() != pLSeq)
2676 {
2677 if (pLDo && (pLSeq != 0))
2678 {
2679 JLOG(pJournal_.debug()) << "GetObj: Full fetch pack for " << pLSeq;
2680 }
2681 pLSeq = obj.ledgerseq();
2682 pLDo = !app_.getLedgerMaster().haveLedger(pLSeq);
2683
2684 if (!pLDo)
2685 {
2686 JLOG(pJournal_.debug()) << "GetObj: Late fetch pack for " << pLSeq;
2687 }
2688 else
2689 {
2690 progress = true;
2691 }
2692 }
2693 }
2694
2695 if (pLDo)
2696 {
2697 uint256 const hash = uint256::fromRaw(obj.hash());
2698
2699 app_.getLedgerMaster().addFetchPack(
2700 hash, std::make_shared<Blob>(obj.data().begin(), obj.data().end()));
2701 }
2702 }
2703 }
2704
2705 if (pLDo && (pLSeq != 0))
2706 {
2707 JLOG(pJournal_.debug()) << "GetObj: Partial fetch pack for " << pLSeq;
2708 }
2709 if (packet.type() == protocol::TMGetObjectByHash::otFETCH_PACK)
2710 app_.getLedgerMaster().gotFetchPack(progress, pLSeq);
2711 }
2712}
2713
2714void
2716{
2717 protocol::TMGetObjectByHash const& packet = *m;
2718
2719 protocol::TMGetObjectByHash reply;
2720 reply.set_query(false);
2721 reply.set_type(packet.type());
2722
2723 if (packet.has_ledgerhash())
2724 {
2725 reply.set_ledgerhash(packet.ledgerhash());
2726 }
2727
2728 // Defense in depth: caller (onMessage) already validates cheap
2729 // structural properties of the request before dispatching here:
2730 // - objects_size() <= kHardMaxReplyNodes (oversize gate)
2731 // - if has_ledgerhash() then ledgerhash is uint256-sized
2732 // The iteration cap below mirrors the oversize gate so this method
2733 // remains safe if invoked directly by tests or future callers, and
2734 // a peer cannot drive unbounded NodeStore lookups by sending
2735 // non-existent hashes.
2736 int const requested = packet.objects_size();
2737 int const iterLimit = std::min(requested, tuning::kHardMaxReplyNodes);
2738
2739 for (int i = 0; i < iterLimit; ++i)
2740 {
2741 auto const& obj = packet.objects(i);
2742 if (!obj.has_hash() || !stringIsUInt256Sized(obj.hash()))
2743 continue;
2744
2745 uint256 const hash = uint256::fromRaw(obj.hash());
2746 // VFALCO TODO Move this someplace more sensible so we don't
2747 // need to inject the NodeStore interfaces.
2748 std::uint32_t const seq{obj.has_ledgerseq() ? obj.ledgerseq() : 0};
2749 auto const nodeObject = app_.getNodeStore().fetchNodeObject(hash, seq);
2750 if (!nodeObject)
2751 continue;
2752
2753 protocol::TMIndexedObject& newObj = *reply.add_objects();
2754 newObj.set_hash(hash.begin(), hash.size());
2755 auto const& data = nodeObject->getData();
2756 newObj.set_data(data.data(), data.size());
2757 if (obj.has_nodeid())
2758 newObj.set_index(obj.nodeid());
2759 if (obj.has_ledgerseq())
2760 newObj.set_ledgerseq(obj.ledgerseq());
2761 }
2762
2763 // Apply work-proportional charge. `charge()` posts the disconnect
2764 // step (if any) back to strand_, so it is safe to call from this
2765 // JobQueue worker thread.
2766 charge(
2767 // We pass `requested` directly here, instead of actual lookups done. Which could be
2768 // std::min(packet.objects_size(), static_cast<int>(tuning::kHardMaxReplyNodes));
2769 // Because we want to charge as per the request size, to discourage large requests.
2770 computeGetObjectByHashFee(requested, reply.objects_size()),
2771 "processed get object by hash request");
2772
2773 JLOG(pJournal_.trace()) << "GetObj: " << reply.objects_size() << " of " << requested;
2774 send(std::make_shared<Message>(reply, protocol::mtGET_OBJECTS));
2775}
2776
2777void
2779{
2780 if (!txReduceRelayEnabled())
2781 {
2782 JLOG(pJournal_.error()) << "TMHaveTransactions: tx reduce-relay is disabled";
2783 fee_.update(resource::kFeeMalformedRequest, "disabled");
2784 return;
2785 }
2786
2788 app_.getJobQueue().addJob(JtMissingTxn, "HandleHaveTxs", [weak, m]() {
2789 if (auto peer = weak.lock())
2790 peer->handleHaveTransactions(m);
2791 });
2792}
2793
2794void
2796{
2797 protocol::TMGetObjectByHash tmBH;
2798 tmBH.set_type(protocol::TMGetObjectByHash_ObjectType_otTRANSACTIONS);
2799 tmBH.set_query(true);
2800
2801 JLOG(pJournal_.trace()) << "received TMHaveTransactions " << m->hashes_size();
2802
2803 for (std::uint32_t i = 0; i < m->hashes_size(); i++)
2804 {
2805 if (!stringIsUInt256Sized(m->hashes(i)))
2806 {
2807 JLOG(pJournal_.error()) << "TMHaveTransactions with invalid hash size";
2808 fee_.update(resource::kFeeMalformedRequest, "hash size");
2809 return;
2810 }
2811
2812 uint256 hash = uint256::fromRaw(m->hashes(i));
2813
2814 auto txn = app_.getMasterTransaction().fetchFromCache(hash);
2815
2816 JLOG(pJournal_.trace()) << "checking transaction " << (bool)txn;
2817
2818 if (!txn)
2819 {
2820 JLOG(pJournal_.debug()) << "adding transaction to request";
2821
2822 auto obj = tmBH.add_objects();
2823 obj->set_hash(hash.data(), hash.size());
2824 }
2825 else
2826 {
2827 // Erase only if a peer has seen this tx. If the peer has not
2828 // seen this tx then the tx could not has been queued for this
2829 // peer.
2830 removeTxQueue(hash);
2831 }
2832 }
2833
2834 JLOG(pJournal_.trace()) << "transaction request object is " << tmBH.objects_size();
2835
2836 if (tmBH.objects_size() > 0)
2837 send(std::make_shared<Message>(tmBH, protocol::mtGET_OBJECTS));
2838}
2839
2840void
2842{
2843 if (!txReduceRelayEnabled())
2844 {
2845 JLOG(pJournal_.error()) << "TMTransactions: tx reduce-relay is disabled";
2846 fee_.update(resource::kFeeMalformedRequest, "disabled");
2847 return;
2848 }
2849
2850 JLOG(pJournal_.trace()) << "received TMTransactions " << m->transactions_size();
2851
2852 overlay_.addTxMetrics(m->transactions_size());
2853
2854 for (std::uint32_t i = 0; i < m->transactions_size(); ++i)
2855 {
2858 m->mutable_transactions(i), [](protocol::TMTransaction*) {}),
2859 false,
2860 true);
2861 }
2862}
2863
2864void
2866{
2867 dispatch(strand_, [self = shared_from_this(), m]() {
2868 if (!m->has_validatorpubkey())
2869 {
2870 self->fee_.update(resource::kFeeInvalidData, "squelch no pubkey");
2871 return;
2872 }
2873 auto validator = m->validatorpubkey();
2874 auto const slice{makeSlice(validator)};
2875 if (!publicKeyType(slice))
2876 {
2877 self->fee_.update(resource::kFeeInvalidData, "squelch bad pubkey");
2878 return;
2879 }
2880 PublicKey const key(slice);
2881
2882 // Ignore the squelch for validator's own messages.
2883 if (key == self->app_.getValidationPublicKey())
2884 {
2885 JLOG(self->pJournal_.debug())
2886 << "onMessage: TMSquelch discarding validator's squelch " << slice;
2887 return;
2888 }
2889
2890 std::uint32_t const duration = m->has_squelchduration() ? m->squelchduration() : 0;
2891 if (!m->squelch())
2892 {
2893 self->squelch_.removeSquelch(key);
2894 }
2895 else if (!self->squelch_.addSquelch(key, std::chrono::seconds{duration}))
2896 {
2897 self->fee_.update(resource::kFeeInvalidData, "squelch duration");
2898 }
2899
2900 JLOG(self->pJournal_.debug())
2901 << "onMessage: TMSquelch " << slice << " " << self->id() << " " << duration;
2902 });
2903}
2904
2905//--------------------------------------------------------------------------
2906
2907void
2908PeerImp::addLedger(uint256 const& hash, std::scoped_lock<std::mutex> const& lockedRecentLock)
2909{
2910 // lockedRecentLock is passed as a reminder that recentLock_ must be
2911 // locked by the caller.
2912 (void)lockedRecentLock;
2913
2915 return;
2916
2917 recentLedgers_.push_back(hash);
2918}
2919
2920void
2922{
2923 // VFALCO TODO Invert this dependency using an observer and shared state
2924 // object. Don't queue fetch pack jobs if we're under load or we already
2925 // have some queued.
2926 if (app_.getFeeTrack().isLoadedLocal() ||
2927 (app_.getLedgerMaster().getValidatedLedgerAge() > 40s) ||
2928 (app_.getJobQueue().getJobCount(JtPack) > 10))
2929 {
2930 JLOG(pJournal_.info()) << "Too busy to make fetch pack";
2931 return;
2932 }
2933
2934 if (!stringIsUInt256Sized(packet->ledgerhash()))
2935 {
2936 JLOG(pJournal_.warn()) << "FetchPack hash size malformed";
2937 fee_.update(resource::kFeeMalformedRequest, "hash size");
2938 return;
2939 }
2940
2942
2943 uint256 const hash = uint256::fromRaw(packet->ledgerhash());
2944
2946 auto elapsed = UptimeClock::now();
2947 auto const pap = &app_;
2948 app_.getJobQueue().addJob(JtPack, "MakeFetchPack", [pap, weak, packet, hash, elapsed]() {
2949 pap->getLedgerMaster().makeFetchPack(weak, packet, hash, elapsed);
2950 });
2951}
2952
2953void
2955{
2956 protocol::TMTransactions reply;
2957
2958 JLOG(pJournal_.trace()) << "received TMGetObjectByHash requesting tx "
2959 << packet->objects_size();
2960
2961 if (packet->objects_size() > reduce_relay::kMaxTxQueueSize)
2962 {
2963 JLOG(pJournal_.error()) << "doTransactions, invalid number of hashes";
2964 fee_.update(resource::kFeeMalformedRequest, "too big");
2965 return;
2966 }
2967
2968 for (std::uint32_t i = 0; i < packet->objects_size(); ++i)
2969 {
2970 auto const& obj = packet->objects(i);
2971
2972 if (!stringIsUInt256Sized(obj.hash()))
2973 {
2974 fee_.update(resource::kFeeMalformedRequest, "hash size");
2975 return;
2976 }
2977
2978 uint256 hash = uint256::fromRaw(obj.hash());
2979
2980 auto txn = app_.getMasterTransaction().fetchFromCache(hash);
2981
2982 if (!txn)
2983 {
2984 JLOG(pJournal_.error())
2985 << "doTransactions, transaction not found " << Slice(hash.data(), hash.size());
2986 fee_.update(resource::kFeeMalformedRequest, "tx not found");
2987 return;
2988 }
2989
2990 Serializer s;
2991 auto tx = reply.add_transactions();
2992 auto sttx = txn->getSTransaction();
2993 sttx->add(s);
2994 tx->set_rawtransaction(s.data(), s.size());
2995 tx->set_status(
2996 txn->getStatus() == TransStatus::INCLUDED ? protocol::tsCURRENT : protocol::tsNEW);
2997 tx->set_receivetimestamp(app_.getTimeKeeper().now().time_since_epoch().count());
2998 tx->set_deferred(txn->getSubmitResult().queued);
2999 }
3000
3001 if (reply.transactions_size() > 0)
3002 send(std::make_shared<Message>(reply, protocol::mtTRANSACTIONS));
3003}
3004
3005void
3007 HashRouterFlags flags,
3008 bool checkSignature,
3009 std::shared_ptr<STTx const> const& stx,
3010 bool batch)
3011{
3012 // VFALCO TODO Rewrite to not use exceptions
3013 try
3014 {
3015 // charge strongly for relaying batch txns
3016 // LCOV_EXCL_START
3017 /*
3018 There is no need to check whether the featureBatchV1_1 amendment is
3019 enabled.
3020
3021 * If the `tfInnerBatchTxn` flag is set, and the amendment is
3022 enabled, then it's an invalid transaction because inner batch
3023 transactions should not be relayed.
3024 * If the `tfInnerBatchTxn` flag is set, and the amendment is *not*
3025 enabled, then the transaction is malformed because it's using an
3026 "unknown" flag. There's no need to waste the resources to send it
3027 to the transaction engine.
3028
3029 We don't normally check transaction validity at this level, but
3030 since we _need_ to check it when the amendment is enabled, we may as
3031 well drop it if the flag is set regardless.
3032 */
3033 if (stx->isFlag(tfInnerBatchTxn))
3034 {
3035 JLOG(pJournal_.warn()) << "Ignoring Network relayed Tx containing "
3036 "tfInnerBatchTxn (checkSignature).";
3037 charge(resource::kFeeModerateBurdenPeer, "inner batch txn");
3038 return;
3039 }
3040 // LCOV_EXCL_STOP
3041
3042 // Expired?
3043 if (stx->isFieldPresent(sfLastLedgerSequence) &&
3044 (stx->getFieldU32(sfLastLedgerSequence) < app_.getLedgerMaster().getValidLedgerIndex()))
3045 {
3046 JLOG(pJournal_.info()) << "Marking transaction " << stx->getTransactionID()
3047 << "as BAD because it's expired";
3048 app_.getHashRouter().setFlags(stx->getTransactionID(), HashRouterFlags::BAD);
3049 charge(resource::kFeeUselessData, "expired tx");
3050 return;
3051 }
3052
3053 if (isPseudoTx(*stx))
3054 {
3055 // Don't do anything with pseudo transactions except put them in the
3056 // TransactionMaster cache
3057 std::string reason;
3058 auto tx = std::make_shared<Transaction>(stx, reason, app_);
3059 XRPL_ASSERT(
3060 tx->getStatus() == TransStatus::NEW,
3061 "xrpl::PeerImp::checkTransaction Transaction created "
3062 "correctly");
3063 if (tx->getStatus() == TransStatus::NEW)
3064 {
3065 JLOG(pJournal_.debug()) << "Processing " << (batch ? "batch" : "unsolicited")
3066 << " pseudo-transaction tx " << tx->getID();
3067
3068 app_.getMasterTransaction().canonicalize(&tx);
3069 // Tell the overlay about it, but don't relay it.
3070 auto const toSkip = app_.getHashRouter().shouldRelay(tx->getID());
3071 if (toSkip)
3072 {
3073 JLOG(pJournal_.debug())
3074 << "Passing skipped pseudo pseudo-transaction tx " << tx->getID();
3075 app_.getOverlay().relay(tx->getID(), {}, *toSkip);
3076 }
3077 if (!batch)
3078 {
3079 JLOG(pJournal_.debug()) << "Charging for pseudo-transaction tx " << tx->getID();
3080 charge(resource::kFeeUselessData, "pseudo tx");
3081 }
3082
3083 return;
3084 }
3085 }
3086
3087 if (checkSignature)
3088 {
3089 // Check the signature before handing off to the job queue.
3090 if (auto [valid, validReason] = checkValidity(
3091 app_.getHashRouter(), *stx, app_.getLedgerMaster().getValidatedRules());
3093 {
3094 if (!validReason.empty())
3095 {
3096 JLOG(pJournal_.debug()) << "Exception checking transaction: " << validReason;
3097 }
3098
3099 // Probably not necessary to set HashRouterFlags::BAD, but
3100 // doesn't hurt.
3101 app_.getHashRouter().setFlags(stx->getTransactionID(), HashRouterFlags::BAD);
3102 charge(resource::kFeeInvalidSignature, "check transaction signature failure");
3103 return;
3104 }
3105 }
3106 else
3107 {
3108 forceValidity(app_.getHashRouter(), stx->getTransactionID(), Validity::Valid);
3109 }
3110
3111 std::string reason;
3112 auto tx = std::make_shared<Transaction>(stx, reason, app_);
3113
3114 if (tx->getStatus() == TransStatus::INVALID)
3115 {
3116 if (!reason.empty())
3117 {
3118 JLOG(pJournal_.debug()) << "Exception checking transaction: " << reason;
3119 }
3120 app_.getHashRouter().setFlags(stx->getTransactionID(), HashRouterFlags::BAD);
3121 charge(resource::kFeeInvalidSignature, "tx (impossible)");
3122 return;
3123 }
3124
3125 bool const trusted = any(flags & HashRouterFlags::TRUSTED);
3126 app_.getOPs().processTransaction(tx, trusted, false, NetworkOPs::FailHard::No);
3127 }
3128 catch (std::exception const& ex)
3129 {
3130 JLOG(pJournal_.warn()) << "Exception in " << __func__ << ": " << ex.what();
3131 app_.getHashRouter().setFlags(stx->getTransactionID(), HashRouterFlags::BAD);
3132 using namespace std::string_literals;
3133 charge(resource::kFeeInvalidData, "tx "s + ex.what());
3134 }
3135}
3136
3137// Called from our JobQueue
3138void
3140 bool isTrusted,
3142 RCLCxPeerPos peerPos)
3143{
3144 JLOG(pJournal_.trace()) << "Checking " << (isTrusted ? "trusted" : "UNTRUSTED") << " proposal";
3145
3146 XRPL_ASSERT(packet, "xrpl::PeerImp::checkPropose : non-null packet");
3147
3148 if (!cluster() && !peerPos.checkSign())
3149 {
3150 std::string const desc{"Proposal fails sig check"};
3151 JLOG(pJournal_.warn()) << desc;
3153 return;
3154 }
3155
3156 bool relay = false;
3157
3158 if (isTrusted)
3159 {
3160 relay = app_.getOPs().processTrustedProposal(peerPos);
3161 }
3162 else
3163 {
3164 relay = app_.config().relayUntrustedProposals == 1 || cluster();
3165 }
3166
3167 if (relay)
3168 {
3169 // haveMessage contains peers, which are suppressed; i.e. the peers
3170 // are the source of the message, consequently the message should
3171 // not be relayed to these peers. But the message must be counted
3172 // as part of the squelch logic.
3173 auto haveMessage =
3174 app_.getOverlay().relay(*packet, peerPos.suppressionID(), peerPos.publicKey());
3175 if (!haveMessage.empty())
3176 {
3177 overlay_.updateSlotAndSquelch(
3178 peerPos.suppressionID(),
3179 peerPos.publicKey(),
3180 std::move(haveMessage),
3181 protocol::mtPROPOSE_LEDGER);
3182 }
3183 }
3184}
3185
3186void
3189 uint256 const& key,
3191{
3192 if (!val->isValid())
3193 {
3194 std::string const desc{"Validation forwarded by peer is invalid"};
3195 JLOG(pJournal_.debug()) << desc;
3197 return;
3198 }
3199
3200 // FIXME it should be safe to remove this try/catch. Investigate codepaths.
3201 try
3202 {
3203 if (app_.getOPs().recvValidation(val, std::to_string(id())) || cluster())
3204 {
3205 // haveMessage contains peers, which are suppressed; i.e. the peers
3206 // are the source of the message, consequently the message should
3207 // not be relayed to these peers. But the message must be counted
3208 // as part of the squelch logic.
3209 auto haveMessage = overlay_.relay(*packet, key, val->getSignerPublic());
3210 if (!haveMessage.empty())
3211 {
3212 overlay_.updateSlotAndSquelch(
3213 key, val->getSignerPublic(), std::move(haveMessage), protocol::mtVALIDATION);
3214 }
3215 }
3216 }
3217 catch (std::exception const& ex)
3218 {
3219 JLOG(pJournal_.trace()) << "Exception processing validation: " << ex.what();
3220 using namespace std::string_literals;
3221 charge(resource::kFeeMalformedRequest, "validation "s + ex.what());
3222 }
3223}
3224
3225// Returns the set of peers that can help us get
3226// the TX tree with the specified root hash.
3227//
3229getPeerWithTree(OverlayImpl& ov, uint256 const& rootHash, PeerImp const* skip)
3230{
3232 int retScore = 0;
3233
3234 ov.forEach([&](std::shared_ptr<PeerImp>&& p) {
3235 if (p->hasTxSet(rootHash) && p.get() != skip)
3236 {
3237 auto score = p->getScore(true);
3238 if (!ret || (score > retScore))
3239 {
3240 ret = std::move(p);
3241 retScore = score;
3242 }
3243 }
3244 });
3245
3246 return ret;
3247}
3248
3249// Returns a random peer weighted by how likely to
3250// have the ledger and how responsive it is.
3251//
3254 OverlayImpl& ov,
3255 uint256 const& ledgerHash,
3256 LedgerIndex ledger,
3257 PeerImp const* skip)
3258{
3260 int retScore = 0;
3261
3262 ov.forEach([&](std::shared_ptr<PeerImp>&& p) {
3263 if (p->hasLedger(ledgerHash, ledger) && p.get() != skip)
3264 {
3265 auto score = p->getScore(true);
3266 if (!ret || (score > retScore))
3267 {
3268 ret = std::move(p);
3269 retScore = score;
3270 }
3271 }
3272 });
3273
3274 return ret;
3275}
3276
3277void
3279 std::shared_ptr<Ledger const> const& ledger,
3280 protocol::TMLedgerData& ledgerData)
3281{
3282 JLOG(pJournal_.trace()) << "sendLedgerBase: Base data";
3283
3284 Serializer s(sizeof(LedgerHeader));
3285 addRaw(ledger->header(), s);
3286 ledgerData.add_nodes()->set_nodedata(s.getDataPtr(), s.getLength());
3287
3288 auto const& stateMap{ledger->stateMap()};
3289 if (stateMap.getHash() != beast::kZero)
3290 {
3291 // Return account state root node if possible
3292 Serializer root(768);
3293
3294 stateMap.serializeRoot(root);
3295 ledgerData.add_nodes()->set_nodedata(root.getDataPtr(), root.getLength());
3296
3297 if (ledger->header().txHash != beast::kZero)
3298 {
3299 auto const& txMap{ledger->txMap()};
3300 if (txMap.getHash() != beast::kZero)
3301 {
3302 // Return TX root node if possible
3303 root.erase();
3304 txMap.serializeRoot(root);
3305 ledgerData.add_nodes()->set_nodedata(root.getDataPtr(), root.getLength());
3306 }
3307 }
3308 }
3309
3310 auto message{std::make_shared<Message>(ledgerData, protocol::mtLEDGER_DATA)};
3311 send(message);
3312}
3313
3316{
3317 JLOG(pJournal_.trace()) << "getLedger: Ledger";
3318
3320
3321 if (m->has_ledgerhash())
3322 {
3323 // Attempt to find ledger by hash
3324 uint256 const ledgerHash = uint256::fromRaw(m->ledgerhash());
3325 ledger = app_.getLedgerMaster().getLedgerByHash(ledgerHash);
3326 if (!ledger)
3327 {
3328 JLOG(pJournal_.trace()) << "getLedger: Don't have ledger with hash " << ledgerHash;
3329
3330 if (m->has_querytype() && !m->has_requestcookie())
3331 {
3332 // Attempt to relay the request to a peer
3333 if (auto const peer = getPeerWithLedger(
3334 overlay_, ledgerHash, m->has_ledgerseq() ? m->ledgerseq() : 0, this))
3335 {
3336 m->set_requestcookie(id());
3337 peer->send(std::make_shared<Message>(*m, protocol::mtGET_LEDGER));
3338 JLOG(pJournal_.debug()) << "getLedger: Request relayed to peer";
3339 return ledger;
3340 }
3341
3342 JLOG(pJournal_.trace()) << "getLedger: Failed to find peer to relay request";
3343 }
3344 }
3345 }
3346 else if (m->has_ledgerseq())
3347 {
3348 // Attempt to find ledger by sequence
3349 if (m->ledgerseq() < app_.getLedgerMaster().getEarliestFetch())
3350 {
3351 JLOG(pJournal_.debug()) << "getLedger: Early ledger sequence request";
3352 }
3353 else
3354 {
3355 ledger = app_.getLedgerMaster().getLedgerBySeq(m->ledgerseq());
3356 if (!ledger)
3357 {
3358 JLOG(pJournal_.debug())
3359 << "getLedger: Don't have ledger with sequence " << m->ledgerseq();
3360 }
3361 }
3362 }
3363 else if (m->has_ltype() && m->ltype() == protocol::ltCLOSED)
3364 {
3365 ledger = app_.getLedgerMaster().getClosedLedger();
3366 }
3367
3368 if (ledger)
3369 {
3370 // Validate retrieved ledger sequence
3371 auto const ledgerSeq{ledger->header().seq};
3372 if (m->has_ledgerseq())
3373 {
3374 if (ledgerSeq != m->ledgerseq())
3375 {
3376 // Do not resource charge a peer responding to a relay
3377 if (!m->has_requestcookie())
3378 charge(resource::kFeeMalformedRequest, "get_ledger ledgerSeq");
3379
3380 ledger.reset();
3381 JLOG(pJournal_.warn()) << "getLedger: Invalid ledger sequence " << ledgerSeq;
3382 }
3383 }
3384 else if (ledgerSeq < app_.getLedgerMaster().getEarliestFetch())
3385 {
3386 ledger.reset();
3387 JLOG(pJournal_.debug()) << "getLedger: Early ledger sequence request " << ledgerSeq;
3388 }
3389 }
3390 else
3391 {
3392 JLOG(pJournal_.debug()) << "getLedger: Unable to find ledger";
3393 }
3394
3395 return ledger;
3396}
3397
3400{
3401 JLOG(pJournal_.trace()) << "getTxSet: TX set";
3402
3403 uint256 const txSetHash = uint256::fromRaw(m->ledgerhash());
3404 std::shared_ptr<SHAMap> shaMap{app_.getInboundTransactions().getSet(txSetHash, false)};
3405 if (!shaMap)
3406 {
3407 if (m->has_querytype() && !m->has_requestcookie())
3408 {
3409 // Attempt to relay the request to a peer
3410 if (auto const peer = getPeerWithTree(overlay_, txSetHash, this))
3411 {
3412 m->set_requestcookie(id());
3413 peer->send(std::make_shared<Message>(*m, protocol::mtGET_LEDGER));
3414 JLOG(pJournal_.debug()) << "getTxSet: Request relayed";
3415 }
3416 else
3417 {
3418 JLOG(pJournal_.debug()) << "getTxSet: Failed to find relay peer";
3419 }
3420 }
3421 else
3422 {
3423 JLOG(pJournal_.debug()) << "getTxSet: Failed to find TX set";
3424 }
3425 }
3426
3427 return shaMap;
3428}
3429
3430void
3434{
3437 SHAMap const* map{nullptr};
3438 protocol::TMLedgerData ledgerData;
3439 bool fatLeaves{true};
3440 auto const itype{m->itype()};
3441
3442 if (itype == protocol::liTS_CANDIDATE)
3443 {
3444 if (sharedMap = getTxSet(m); !sharedMap)
3445 return;
3446 map = sharedMap.get();
3447
3448 // Fill out the reply
3449 ledgerData.set_ledgerseq(0);
3450 ledgerData.set_ledgerhash(m->ledgerhash());
3451 ledgerData.set_type(protocol::liTS_CANDIDATE);
3452 if (m->has_requestcookie())
3453 ledgerData.set_requestcookie(m->requestcookie());
3454
3455 // We'll already have most transactions
3456 fatLeaves = false;
3457 }
3458 else
3459 {
3460 if (sendQueue_.size() >= tuning::kDropSendQueue)
3461 {
3462 JLOG(pJournal_.debug()) << "processLedgerRequest: Large send queue";
3463 return;
3464 }
3465 if (app_.getFeeTrack().isLoadedLocal() && !cluster())
3466 {
3467 JLOG(pJournal_.debug()) << "processLedgerRequest: Too busy";
3468 return;
3469 }
3470
3471 if (ledger = getLedger(m); !ledger)
3472 return;
3473
3474 // Fill out the reply
3475 auto const ledgerHash{ledger->header().hash};
3476 ledgerData.set_ledgerhash(ledgerHash.begin(), ledgerHash.size());
3477 ledgerData.set_ledgerseq(ledger->header().seq);
3478 ledgerData.set_type(itype);
3479 if (m->has_requestcookie())
3480 ledgerData.set_requestcookie(m->requestcookie());
3481
3482 switch (itype)
3483 {
3484 case protocol::liBASE:
3485 sendLedgerBase(ledger, ledgerData);
3486 return;
3487
3488 case protocol::liTX_NODE:
3489 map = &ledger->txMap();
3490 JLOG(pJournal_.trace())
3491 << "processLedgerRequest: TX map hash " << to_string(map->getHash());
3492 break;
3493
3494 case protocol::liAS_NODE:
3495 map = &ledger->stateMap();
3496 JLOG(pJournal_.trace())
3497 << "processLedgerRequest: Account state map hash " << to_string(map->getHash());
3498 break;
3499
3500 default:
3501 // This case should not be possible here
3502 JLOG(pJournal_.error()) << "processLedgerRequest: Invalid ledger info type";
3503 return;
3504 }
3505 }
3506
3507 if (map == nullptr)
3508 {
3509 JLOG(pJournal_.warn()) << "processLedgerRequest: Unable to find map";
3510 return;
3511 }
3512
3513 // Add requested node data to reply
3514 if (!nodeIDs.empty())
3515 {
3516 std::uint32_t const defaultDepth = isHighLatency() ? 2 : 1;
3517 auto const queryDepth{m->has_querydepth() ? m->querydepth() : defaultDepth};
3518
3520 data.reserve(tuning::kSoftMaxReplyNodes);
3521 auto const useLedgerNodeDepth = supportsFeature(ProtocolFeature::LedgerNodeDepth);
3522
3523 for (auto const& nodeID : nodeIDs)
3524 {
3525 if (ledgerData.nodes_size() >= tuning::kSoftMaxReplyNodes)
3526 break;
3527
3528 data.clear();
3529
3530 try
3531 {
3532 if (map->getNodeFat(nodeID, data, fatLeaves, queryDepth))
3533 {
3534 JLOG(pJournal_.trace())
3535 << "processLedgerRequest: getNodeFat got " << data.size() << " nodes";
3536
3537 for (auto const& d : data)
3538 {
3539 if (ledgerData.nodes_size() >= tuning::kHardMaxReplyNodes)
3540 break;
3541
3542 protocol::TMLedgerNode* node{ledgerData.add_nodes()};
3543 node->set_nodedata(d.data.data(), d.data.size());
3544
3545 // When the LedgerNodeDepth protocol feature is not supported by the peer,
3546 // we always set the `nodeid` field. However, when it is supported then we
3547 // set the `id` field for inner nodes and the `depth` field for leaf nodes.
3548 if (!useLedgerNodeDepth)
3549 {
3550 node->set_nodeid(d.nodeID.getRawString());
3551 }
3552 else if (d.isLeaf)
3553 {
3554 REACHABLE("xrpl::PeerImp : emit leaf depth in reply");
3555 node->set_depth(d.nodeID.getDepth());
3556 }
3557 else
3558 {
3559 REACHABLE("xrpl::PeerImp : emit inner id in reply");
3560 node->set_id(d.nodeID.getRawString());
3561 }
3562 }
3563 }
3564 else
3565 {
3566 JLOG(pJournal_.warn()) << "processLedgerRequest: getNodeFat returns false";
3567 }
3568 }
3569 catch (std::exception const& e)
3570 {
3571 std::string info;
3572 switch (itype)
3573 {
3574 case protocol::liBASE:
3575 // This case should not be possible here
3576 info = "Ledger base";
3577 break;
3578
3579 case protocol::liTX_NODE:
3580 info = "TX node";
3581 break;
3582
3583 case protocol::liAS_NODE:
3584 info = "AS node";
3585 break;
3586
3587 case protocol::liTS_CANDIDATE:
3588 info = "TS candidate";
3589 break;
3590
3591 default:
3592 info = "Invalid";
3593 break;
3594 }
3595
3596 if (!m->has_ledgerhash())
3597 info += ", no hash specified";
3598
3599 JLOG(pJournal_.warn())
3600 << "processLedgerRequest: getNodeFat with nodeId " << nodeID
3601 << " and ledger info type " << info << " throws exception: " << e.what();
3602 }
3603 }
3604
3605 JLOG(pJournal_.info()) << "processLedgerRequest: Got request for " << m->nodeids_size()
3606 << " node IDs at depth " << queryDepth << ", return "
3607 << ledgerData.nodes_size() << " nodes";
3608 }
3609
3610 if (ledgerData.nodes_size() == 0)
3611 return;
3612
3613 send(std::make_shared<Message>(ledgerData, protocol::mtLEDGER_DATA));
3614}
3615
3616// Differential pricing helper. Returns only the *dynamic* component
3617// of the per-message charge — the base `kFeeModerateBurdenPeer` is
3618// applied at admission time in `onMessage(TMGetObjectByHash)` so a
3619// high traffic client pays for the message regardless of when (or
3620// whether) the worker runs.
3621//
3622// Dynamic charge model:
3623//
3624// billable = max(0, requested - kFreeObjectsPerRequest)
3625// missed = max(0, requested - found)
3626// billableMisses = min(missed, billable) // misses billed first
3627// billableHits = billable - billableMisses
3628// sizeBand = (requested > kBandMediumMax) ? kCostBandLarge
3629// : (requested > kBandSmallMax) ? kCostBandMedium
3630// : kCostBandSmall
3631// dynamic = billableHits * kCostPerLookupHit
3632// + billableMisses * kCostPerLookupMiss
3633// + sizeBand
3634//
3635// Misses are billed first against the billable budget because a node store
3636// seek dominates a cache hit and because invalid hashes are ~100% miss by construction.
3638PeerImp::computeGetObjectByHashFee(int const requested, int const found)
3639{
3640 int const billable = std::max(0, requested - static_cast<int>(tuning::kFreeObjectsPerRequest));
3641 // Clamp `missed` so a future caller passing found > requested cannot
3642 // produce a negative value that flips the hits/misses split.
3643 int const missed = std::max(0, requested - found);
3644 int const billableMisses = std::min(missed, billable);
3645 int const billableHits = billable - billableMisses;
3646
3647 int sizeBand = tuning::kCostBandSmall;
3648 if (requested > tuning::kBandMediumMax)
3649 {
3650 sizeBand = tuning::kCostBandLarge;
3651 }
3652 else if (requested > tuning::kBandSmallMax)
3653 {
3654 sizeBand = tuning::kCostBandMedium;
3655 }
3656
3657 int const dynamic = (billableHits * tuning::kCostPerLookupHit) +
3658 (billableMisses * tuning::kCostPerLookupMiss) + sizeBand;
3659
3660 return resource::Charge(dynamic, "GetObject differential");
3661}
3662
3663int
3664PeerImp::getScore(bool haveItem) const
3665{
3666 // Random component of score, used to break ties and avoid
3667 // overloading the "best" peer
3668 static int const kSpRandomMax = 9999;
3669
3670 // Score for being very likely to have the thing we are
3671 // look for; should be roughly spRandomMax
3672 static int const kSpHaveItem = 10000;
3673
3674 // Score reduction for each millisecond of latency; should
3675 // be roughly spRandomMax divided by the maximum reasonable
3676 // latency
3677 static int const kSpLatency = 30;
3678
3679 // Penalty for unknown latency; should be roughly spRandomMax
3680 static int const kSpNoLatency = 8000;
3681
3682 int score = randInt(kSpRandomMax);
3683
3684 if (haveItem)
3685 score += kSpHaveItem;
3686
3688 {
3689 std::scoped_lock const sl(recentLock_);
3690 latency = latency_;
3691 }
3692
3693 if (latency)
3694 {
3695 score -= latency->count() * kSpLatency;
3696 }
3697 else
3698 {
3699 score -= kSpNoLatency;
3700 }
3701
3702 return score;
3703}
3704
3705bool
3707{
3708 std::scoped_lock const sl(recentLock_);
3709 return latency_ >= kPeerHighLatency;
3710}
3711
3712void
3714{
3715 using namespace std::chrono_literals;
3716 std::unique_lock const lock{mutex_};
3717
3718 totalBytes_ += bytes;
3719 accumBytes_ += bytes;
3720 auto const timeElapsed = clock_type::now() - intervalStart_;
3721 auto const timeElapsedInSecs = std::chrono::duration_cast<std::chrono::seconds>(timeElapsed);
3722
3723 if (timeElapsedInSecs >= 1s)
3724 {
3725 auto const avgBytes = accumBytes_ / timeElapsedInSecs.count();
3726 rollingAvg_.push_back(avgBytes);
3727
3728 auto const totalBytes = std::accumulate(rollingAvg_.begin(), rollingAvg_.end(), 0ull);
3730
3732 accumBytes_ = 0;
3733 }
3734}
3735
3738{
3739 std::shared_lock const lock{mutex_};
3740 return rollingAvgBytes_;
3741}
3742
3745{
3746 std::shared_lock const lock{mutex_};
3747 return totalBytes_;
3748}
3749
3750} // namespace xrpl
T accumulate(T... args)
T begin(T... args)
T clamp(T... args)
A version-independent IP address and port combination.
Definition IPEndpoint.h:24
static std::optional< Endpoint > fromStringChecked(std::string const &s)
Create an Endpoint from a string.
static Endpoint fromString(std::string const &s)
Represents a JSON value.
Definition json_value.h:117
static BaseUInt fromRaw(Container const &c)
Definition base_uint.h:302
pointer data()
Definition base_uint.h:117
iterator begin()
Definition base_uint.h:128
static constexpr std::size_t size()
Definition base_uint.h:548
static std::size_t messageSize(::google::protobuf::Message const &message)
Definition Message.cpp:48
std::chrono::time_point< NetClock > time_point
Definition chrono.h:48
std::chrono::duration< rep, period > duration
Definition chrono.h:47
Child(OverlayImpl &overlay)
void forEach(UnaryFunc &&f) const
std::shared_mutex mutex_
Definition PeerImp.h:237
std::uint64_t rollingAvgBytes_
Definition PeerImp.h:242
clock_type::time_point intervalStart_
Definition PeerImp.h:239
boost::circular_buffer< std::uint64_t > rollingAvg_
Definition PeerImp.h:238
void addMessage(std::uint64_t bytes)
Definition PeerImp.cpp:3713
std::uint64_t averageBytes() const
Definition PeerImp.cpp:3737
std::uint64_t totalBytes() const
Definition PeerImp.cpp:3744
std::uint64_t accumBytes_
Definition PeerImp.h:241
std::uint64_t totalBytes_
Definition PeerImp.h:240
void checkTracking(std::uint32_t validationSeq)
Check if the peer is tracking.
Definition PeerImp.cpp:2161
void onTimer(boost::system::error_code const &ec)
Definition PeerImp.cpp:702
std::optional< std::chrono::milliseconds > latency_
Definition PeerImp.h:129
void addTxQueue(uint256 const &hash) override
Add transaction's hash to the transactions' hashes queue.
Definition PeerImp.cpp:354
std::string getVersion() const
Return the version of xrpld that the peer is running, if reported.
Definition PeerImp.cpp:419
void onMessage(std::shared_ptr< protocol::TMManifests > const &m)
Definition PeerImp.cpp:1094
ProtocolVersion protocol_
Definition PeerImp.h:109
bool txReduceRelayEnabled_
Definition PeerImp.h:211
void checkTransaction(HashRouterFlags flags, bool checkSignature, std::shared_ptr< STTx const > const &stx, bool batch)
Definition PeerImp.cpp:3006
void setTimer()
Definition PeerImp.cpp:662
void handleTransaction(std::shared_ptr< protocol::TMTransaction > const &m, bool eraseTxQueue, bool batch)
Called from onMessage(TMTransaction(s)).
Definition PeerImp.cpp:1292
http_request_type request_
Definition PeerImp.h:193
void removeTxQueue(uint256 const &hash) override
Remove transaction's hash from the transactions' hashes queue.
Definition PeerImp.cpp:369
beast::WrappedSink sink_
Definition PeerImp.h:89
std::string name() const
Definition PeerImp.cpp:862
bool txReduceRelayEnabled() const override
Definition PeerImp.h:487
compression::Compressed Compressed
Definition PeerImp.h:83
boost::beast::http::fields const & headers_
Definition PeerImp.h:195
Compressed compressionEnabled_
Definition PeerImp.h:204
std::chrono::steady_clock clock_type
Definition PeerImp.h:75
boost::system::error_code error_code
Definition PeerImp.h:76
LedgerIndex minLedger_
Definition PeerImp.h:121
std::string prefix_
Definition PeerImp.h:88
std::shared_ptr< peer_finder::Slot > const slot_
Definition PeerImp.h:191
void addLedger(uint256 const &hash, std::scoped_lock< std::mutex > const &lockedRecentLock)
Definition PeerImp.cpp:2908
boost::beast::multi_buffer readBuffer_
Definition PeerImp.h:192
id_t const id_
Definition PeerImp.h:86
std::string const & fingerprint() const override
Definition PeerImp.h:573
bool ledgerReplayEnabled_
Definition PeerImp.h:213
void sendTxQueue() override
Send aggregated transactions' hashes.
Definition PeerImp.cpp:338
struct xrpl::PeerImp::@337373043150231020277011015352151251117171316327 metrics_
beast::ip::Endpoint const remoteAddress_
Definition PeerImp.h:101
std::unique_ptr< stream_type > streamPtr_
Definition PeerImp.h:93
uint256 closedLedgerHash_
Definition PeerImp.h:123
reduce_relay::Squelch< UptimeClock > squelch_
Definition PeerImp.h:134
stream_type & stream_
Definition PeerImp.h:95
PeerImp(PeerImp const &)=delete
void checkValidation(std::shared_ptr< STValidation > const &val, uint256 const &key, std::shared_ptr< protocol::TMValidation > const &packet)
Definition PeerImp.cpp:3187
void cycleStatus() override
Definition PeerImp.cpp:586
bool gracefulClose_
Definition PeerImp.h:197
std::shared_mutex nameMutex_
Definition PeerImp.h:117
socket_type & socket_
Definition PeerImp.h:94
std::string domain() const
Definition PeerImp.cpp:869
std::atomic< Tracking > tracking_
Definition PeerImp.h:111
void ledgerRange(std::uint32_t &minSeq, std::uint32_t &maxSeq) const override
Definition PeerImp.cpp:570
clock_type::duration uptime() const
Definition PeerImp.h:399
LedgerIndex maxLedger_
Definition PeerImp.h:122
beast::WrappedSink pSink_
Definition PeerImp.h:90
Application & app_
Definition PeerImp.h:85
PublicKey const publicKey_
Definition PeerImp.h:115
void processGetObjectByHash(std::shared_ptr< protocol::TMGetObjectByHash > const &m)
Process a generic-query TMGetObjectByHash message.
Definition PeerImp.cpp:2715
bool cluster() const override
Returns true if this connection is a member of the cluster.
Definition PeerImp.cpp:413
static resource::Charge computeGetObjectByHashFee(int const requested, int const found)
Compute the per-message resource charge for a TMGetObjectByHash request based on how much work was ac...
Definition PeerImp.cpp:3638
std::queue< std::shared_ptr< Message > > sendQueue_
Definition PeerImp.h:196
void checkPropose(bool isTrusted, std::shared_ptr< protocol::TMProposeSet > const &packet, RCLCxPeerPos peerPos)
Definition PeerImp.cpp:3139
virtual void run()
Definition PeerImp.cpp:205
OverlayImpl & overlay_
Definition PeerImp.h:105
resource::Consumer usage_
Definition PeerImp.h:183
void sendLedgerBase(std::shared_ptr< Ledger const > const &ledger, protocol::TMLedgerData &ledgerData)
Definition PeerImp.cpp:3278
Peer::id_t id() const override
Definition PeerImp.h:361
beast::Journal const pJournal_
Definition PeerImp.h:92
friend class OverlayImpl
Definition PeerImp.h:216
static std::string makePrefix(std::string const &fingerprint)
Definition PeerImp.cpp:694
ChargeWithContext fee_
Definition PeerImp.h:184
void onShutdown(error_code ec)
Definition PeerImp.cpp:762
void handleHaveTransactions(std::shared_ptr< protocol::TMHaveTransactions > const &m)
Handle protocol message with hashes of transactions that have not been relayed by an upstream node do...
Definition PeerImp.cpp:2795
void onMessageUnknown(std::uint16_t type)
Definition PeerImp.cpp:1041
void processLedgerRequest(std::shared_ptr< protocol::TMGetLedger > const &m, std::vector< SHAMapNodeID > nodeIDs)
Definition PeerImp.cpp:3431
~PeerImp() override
Definition PeerImp.cpp:182
void gracefulClose()
Definition PeerImp.cpp:647
Tracking
Whether the peer's view of the ledger converges or diverges from ours.
Definition PeerImp.h:72
protocol::TMStatusChange lastStatus_
Definition PeerImp.h:182
clock_type::time_point const creationTime_
Definition PeerImp.h:132
LedgerReplayMsgHandler ledgerReplayMsgHandler_
Definition PeerImp.h:214
void fail(std::string const &reason)
Definition PeerImp.cpp:622
std::unique_ptr< LoadEvent > loadEvent_
Definition PeerImp.h:199
void send(std::shared_ptr< Message > const &m) override
Definition PeerImp.cpp:276
void onMessageBegin(std::uint16_t type, std::shared_ptr<::google::protobuf::Message > const &m, std::size_t size, std::size_t uncompressedSize, bool isCompressed)
Definition PeerImp.cpp:1047
void doFetchPack(std::shared_ptr< protocol::TMGetObjectByHash > const &packet)
Definition PeerImp.cpp:2921
bool supportsFeature(ProtocolFeature f) const override
Definition PeerImp.cpp:541
void doProtocolStart()
Definition PeerImp.cpp:879
int largeSendq_
Definition PeerImp.h:198
std::shared_ptr< Ledger const > getLedger(std::shared_ptr< protocol::TMGetLedger > const &m)
Definition PeerImp.cpp:3315
bool const inbound_
Definition PeerImp.h:106
boost::asio::strand< boost::asio::executor > strand_
Definition PeerImp.h:96
waitable_timer timer_
Definition PeerImp.h:97
clock_type::time_point lastPingTime_
Definition PeerImp.h:131
boost::circular_buffer< uint256 > recentLedgers_
Definition PeerImp.h:126
int getScore(bool haveItem) const override
Definition PeerImp.cpp:3664
std::string name_
Definition PeerImp.h:116
bool hasTxSet(uint256 const &hash) const override
Definition PeerImp.cpp:579
boost::circular_buffer< uint256 > recentTxSets_
Definition PeerImp.h:127
clock_type::time_point trackingTime_
Definition PeerImp.h:112
beast::Journal const journal_
Definition PeerImp.h:91
std::string fingerprint_
Definition PeerImp.h:87
bool isHighLatency() const override
Definition PeerImp.cpp:3706
void cancelTimer() noexcept
Definition PeerImp.cpp:679
bool hasRange(std::uint32_t uMin, std::uint32_t uMax) override
Definition PeerImp.cpp:596
std::shared_ptr< SHAMap const > getTxSet(std::shared_ptr< protocol::TMGetLedger > const &m) const
Definition PeerImp.cpp:3399
uint256 previousLedgerHash_
Definition PeerImp.h:124
std::optional< std::uint32_t > lastPingSeq_
Definition PeerImp.h:130
void onWriteMessage(error_code ec, std::size_t bytesTransferred)
Definition PeerImp.cpp:989
bool detaching_
Definition PeerImp.h:113
bool crawl() const
Returns true if this connection will publicly share its IP address.
Definition PeerImp.cpp:404
void stop() override
Definition PeerImp.cpp:263
void onValidatorListMessage(std::string const &messageType, std::string const &manifest, std::uint32_t version, std::vector< ValidatorBlobInfo > const &blobs)
Definition PeerImp.cpp:2226
void charge(resource::Charge const &fee, std::string const &context) override
Adjust this peer's load balance based on the type of load imposed.
Definition PeerImp.cpp:378
void doTransactions(std::shared_ptr< protocol::TMGetObjectByHash > const &packet)
Process peer's request to send missing transactions.
Definition PeerImp.cpp:2954
void doAccept()
Definition PeerImp.cpp:788
std::shared_ptr< peer_finder::Slot > const & slot()
Definition PeerImp.h:296
void onReadMessage(error_code ec, std::size_t bytesTransferred)
Definition PeerImp.cpp:917
hash_map< PublicKey, std::size_t > publisherListSequences_
Definition PeerImp.h:202
json::Value json() override
Definition PeerImp.cpp:427
std::mutex recentLock_
Definition PeerImp.h:181
bool hasLedger(uint256 const &hash, std::uint32_t seq) const override
Definition PeerImp.cpp:556
boost::asio::basic_waitable_timer< std::chrono::steady_clock > waitable_timer
Definition PeerImp.h:82
void onMessageEnd(std::uint16_t type, std::shared_ptr<::google::protobuf::Message > const &m)
Definition PeerImp.cpp:1087
std::uint32_t id_t
Uniquely identifies a peer.
A public key.
Definition PublicKey.h:53
Slice slice() const noexcept
Definition PublicKey.h:115
A peer's signed, proposed position for use in RCLConsensus.
uint256 const & suppressionID() const
Unique id used by hash router to suppress duplicates.
PublicKey const & publicKey() const
Public key of peer that sent the proposal.
ConsensusProposal< NodeID, uint256, uint256 > Proposal
bool checkSign() const
Verify the signing hash of the proposal.
bool getNodeFat(SHAMapNodeID const &wanted, std::vector< SHAMapNodeData > &data, bool fatLeaves, std::uint32_t depth) const
SHAMapHash getHash() const
void const * getDataPtr() const
Definition Serializer.h:198
int getLength() const
Definition Serializer.h:208
std::size_t size() const noexcept
Definition Serializer.h:51
void const * data() const noexcept
Definition Serializer.h:57
An immutable linear range of bytes.
Definition Slice.h:28
static Category categorize(::google::protobuf::Message const &message, protocol::MessageType type, bool inbound)
Given a protocol message, determine which traffic category it belongs to.
static time_point now()
static void sendValidatorList(Peer &peer, std::uint64_t peerSequence, PublicKey const &publisherKey, std::size_t maxSequence, std::uint32_t rawVersion, std::string const &rawManifest, std::map< std::size_t, ValidatorBlobInfo > const &blobInfos, HashRouter &hashRouter, beast::Journal j)
static std::vector< ValidatorBlobInfo > parseBlobs(std::uint32_t version, json::Value const &body)
Pull the blob/signature/manifest information out of the appropriate Json body fields depending on the...
A consumption charge.
Definition Charge.h:13
An endpoint that consumes resources.
Definition Consumer.h:20
T duration_cast(T... args)
T emplace_back(T... args)
T empty(T... args)
T end(T... args)
T find(T... args)
T for_each(T... args)
T get(T... args)
T lock(T... args)
T make_shared(T... args)
T max(T... args)
T min(T... args)
constexpr Zero kZero
Definition Zero.h:30
unsigned int UInt
@ Object
object value (collection of name/value pairs).
Definition json_value.h:29
STL namespace.
TER valid(STTx const &tx, ReadView const &view, AccountID const &src, beast::Journal j)
auto measureDurationAndLog(Func &&func, std::string const &actionDescription, std::chrono::duration< Rep, Period > maxDelay, beast::Journal const &journal)
Definition PerfLog.h:162
static constexpr std::size_t kMaxTxQueueSize
static constexpr auto kIdled
Charge const kFeeMalformedData
Charge const kFeeHeavyBurdenPeer
Charge const kFeeUselessData
Charge const kFeeRequestNoReply
Charge const kFeeMalformedRequest
Schedule of fees charged for imposing load on the server.
Charge const kFeeInvalidSignature
Charge const kFeeInvalidData
Charge const kFeeModerateBurdenPeer
Charge const kFeeTrivialPeer
static constexpr auto kSoftMaxReplyNodes
The soft cap on the number of ledger entries in a single reply.
static constexpr auto kBandMediumMax
static constexpr auto kTargetSendQueue
How many messages we consider reasonable sustained on a send queue.
static constexpr auto kCostPerLookupHit
Cost of one cache-hit lookup.
static constexpr auto kCostBandLarge
static constexpr auto kFreeObjectsPerRequest
TMGetObjectByHash differential pricing.
static constexpr auto kHardMaxReplyNodes
The hard cap on the number of ledger entries in a single reply.
static constexpr std::uint32_t kConvergedLedgerLimit
How many ledgers off a server can be and we will still consider it converged.
static constexpr auto kDropSendQueue
How many messages on a send queue before we refuse queries.
static constexpr auto kMaxQueryDepth
The maximum number of levels to search.
static constexpr auto kSendqIntervals
How many timer intervals a sendq has to stay large before we disconnect.
static constexpr auto kBandSmallMax
Cutoffs that decide which size band a request falls into.
constexpr std::size_t kReadBufferBytes
Size of buffer used to read from the socket.
static constexpr auto kCostBandMedium
static constexpr auto kCostPerLookupMiss
Cost of one node-store miss, in units of kCostPerLookupHit.
static constexpr std::uint32_t kDivergedLedgerLimit
How many ledgers off a server has to be before we consider it diverged.
static constexpr auto kCostBandSmall
Size-band surcharges.
static constexpr auto kSendQueueLogFreq
How often to log send queue size.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
bool set(T &target, std::string const &name, Section const &section)
Set a value from a configuration Section If the named value is not found or doesn't parse as a T,...
std::string base64Decode(std::string_view data)
constexpr FlagValue tfInnerBatchTxn
Definition TxFlags.h:44
bool isCurrent(ValidationParms const &p, NetClock::time_point now, NetClock::time_point signTime, NetClock::time_point seenTime)
Whether a validation is still current.
@ Valid
Signature and local checks are good / passed.
Definition apply.h:36
sha512_half_hasher::result_type sha512Half(Args const &... args)
Returns the SHA512-Half of a series of objects.
Definition digest.h:215
std::optional< uint256 > makeSharedValue(stream_type &ssl, beast::Journal journal)
Computes a shared value based on the SSL connection state.
std::optional< AccountID > parseBase58(std::string const &s)
Parse AccountID from checked, base58 string.
std::uint32_t LedgerIndex
A ledger index.
Definition Protocol.h:370
Stopwatch & stopwatch()
Returns an instance of a wall clock.
Definition chrono.h:101
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:13
std::pair< Validity, std::string > checkValidity(HashRouter &router, STTx const &tx, Rules const &rules)
Checks transaction signature and local checks.
Definition apply.cpp:36
@ UnsupportedVersion
List version is not supported.
@ Expired
List is expired, but has the largest non-pending sequence seen so far.
@ SameSequence
Same sequence as current list.
@ KnownSequence
Future sequence already seen.
@ Pending
List will be valid in the future.
@ Accepted
List is valid.
@ Invalid
Invalid format or signature.
@ Untrusted
List signed by untrusted publisher key.
@ Stale
Trusted publisher key, but seq is too old.
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:95
static constexpr char kFeatureLedgerReplay[]
Definition Handshake.h:131
Number root(Number f, unsigned d)
constexpr Dest safeCast(Src s) noexcept
Definition safe_cast.h:21
static std::shared_ptr< PeerImp > getPeerWithLedger(OverlayImpl &ov, uint256 const &ledgerHash, LedgerIndex ledger, PeerImp const *skip)
Definition PeerImp.cpp:3253
std::optional< SHAMapNodeID > getSHAMapNodeID(protocol::TMLedgerNode const &ledgerNode, SHAMapTreeNode const &treeNode)
Extracts or reconstructs the SHAMapNodeID from a ledger node proto message.
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:651
std::optional< KeyType > publicKeyType(Slice const &slice)
Returns the type of public key.
SHAMapTreeNodePtr getTreeNode(std::string_view data)
Deserializes a SHAMapTreeNode from wire format data.
static constexpr char kFeatureTxrr[]
Definition Handshake.h:129
Slice makeSlice(std::array< T, N > const &a)
Definition Slice.h:228
std::pair< std::size_t, boost::system::error_code > invokeProtocolMessage(Buffers const &buffers, Handler &handler, std::size_t &hint)
Calls the handler for up to one protocol message in the passed buffers.
@ JtPack
Definition Job.h:29
@ JtLedgerReq
Definition Job.h:45
@ JtValidationUt
Definition Job.h:40
@ JtTransaction
Definition Job.h:48
@ JtProposalUt
Definition Job.h:46
@ JtReplayReq
Definition Job.h:44
@ JtRequestedTxn
Definition Job.h:50
@ JtTxnData
Definition Job.h:55
@ JtValidationT
Definition Job.h:57
@ JtMissingTxn
Definition Job.h:49
@ JtManifest
Definition Job.h:41
@ JtProposalT
Definition Job.h:60
@ JtPeer
Definition Job.h:66
void addRaw(LedgerHeader const &, Serializer &, bool includeHash=false)
NodeID calcNodeID(PublicKey const &)
Calculate the 160-bit node ID from a node public key.
constexpr ProtocolVersion makeProtocol(std::uint16_t major, std::uint16_t minor)
HashRouterFlags
Definition HashRouter.h:20
std::optional< SHAMapNodeID > deserializeSHAMapNodeID(void const *data, std::size_t size)
Return an object representing a serialized SHAMap Node ID.
http_response_type makeResponse(bool crawlPublic, http_request_type const &req, beast::ip::Address publicIp, beast::ip::Address remoteIp, uint256 const &sharedValue, std::optional< std::uint32_t > networkID, ProtocolVersion protocol, Application &app)
Make http response.
std::string getFingerprint(beast::ip::Endpoint const &address, std::optional< PublicKey > const &publicKey=std::nullopt, std::optional< std::string > const &id=std::nullopt)
Definition PublicKey.h:262
bool peerFeatureEnabled(Headers const &request, std::string const &feature, std::string value, bool config)
Check if a feature should be enabled for a peer.
Definition Handshake.h:182
@ Malformed
Protocol-level violation; no honest peer would produce this.
@ BadData
Peer reported has_error() (legitimate "cannot fulfill" signal).
uint256 proposalUniqueId(uint256 const &proposeHash, uint256 const &previousLedger, std::uint32_t proposeSeq, NetClock::time_point closeTime, Slice const &publicKey, Slice const &signature)
Calculate a unique identifier for a signed proposal.
std::pair< std::uint16_t, std::uint16_t > ProtocolVersion
Represents a particular version of the peer-to-peer protocol.
boost::beast::http::request< boost::beast::http::dynamic_body > http_request_type
Definition Handoff.h:12
void forceValidity(HashRouter &router, uint256 const &txid, Validity validity)
Sets the validity of a given transaction in the cache.
Definition apply.cpp:95
static bool stringIsUInt256Sized(std::string const &pBuffStr)
Definition PeerImp.cpp:199
static constexpr char kFeatureCompr[]
Definition Handshake.h:125
std::string protocolMessageName(int type)
Returns the name of a protocol message given its type.
bool isPseudoTx(STObject const &tx)
Check whether a transaction is a pseudo-transaction.
Definition STTx.cpp:886
BaseUInt< 256 > uint256
Definition base_uint.h:580
static constexpr char kFeatureVprr[]
Definition Handshake.h:127
static std::shared_ptr< PeerImp > getPeerWithTree(OverlayImpl &ov, uint256 const &rootHash, PeerImp const *skip)
Definition PeerImp.cpp:3229
constexpr bool any(HashRouterFlags flags)
Definition HashRouter.h:71
T nth_element(T... args)
T push_back(T... args)
T ref(T... args)
T reserve(T... args)
T reset(T... args)
T size(T... args)
T str(T... args)
Information about the notional ledger backing the view.
Options controlling deserialization of a STValidation.
Describes a single consumer.
Definition Gossip.h:20
beast::ip::Endpoint address
Definition Gossip.h:24
Data format for exchanging consumption information across peers.
Definition Gossip.h:13
std::vector< Item > items
Definition Gossip.h:27
T tie(T... args)
T to_string(T... args)
T what(T... args)