xrpld
Loading...
Searching...
No Matches
NetworkOPs.cpp
1#include <xrpl/server/NetworkOPs.h>
2
3#include <xrpld/app/consensus/RCLConsensus.h>
4#include <xrpld/app/consensus/RCLCxPeerPos.h>
5#include <xrpld/app/consensus/RCLValidations.h>
6#include <xrpld/app/ledger/AcceptedLedger.h>
7#include <xrpld/app/ledger/InboundLedger.h>
8#include <xrpld/app/ledger/InboundLedgers.h>
9#include <xrpld/app/ledger/LedgerMaster.h>
10#include <xrpld/app/ledger/LedgerToJson.h>
11#include <xrpld/app/ledger/LocalTxs.h>
12#include <xrpld/app/ledger/OpenLedger.h>
13#include <xrpld/app/ledger/TransactionMaster.h>
14#include <xrpld/app/main/LoadManager.h>
15#include <xrpld/app/main/Tuning.h>
16#include <xrpld/app/misc/DeliverMax.h>
17#include <xrpld/app/misc/FeeVote.h>
18#include <xrpld/app/misc/Transaction.h>
19#include <xrpld/app/misc/TxQ.h>
20#include <xrpld/app/misc/ValidatorKeys.h>
21#include <xrpld/app/misc/ValidatorList.h>
22#include <xrpld/app/misc/make_NetworkOPs.h>
23#include <xrpld/app/rdb/backend/SQLiteDatabase.h>
24#include <xrpld/core/Config.h>
25#include <xrpld/overlay/Cluster.h>
26#include <xrpld/overlay/ClusterNode.h>
27#include <xrpld/overlay/Overlay.h>
28#include <xrpld/overlay/predicates.h>
29#include <xrpld/rpc/BookChanges.h>
30#include <xrpld/rpc/CTID.h>
31#include <xrpld/rpc/DeliveredAmount.h>
32#include <xrpld/rpc/MPTokenIssuanceID.h>
33#include <xrpld/rpc/ServerHandler.h>
34
35#include <xrpl/basics/Log.h>
36#include <xrpl/basics/ToString.h>
37#include <xrpl/basics/UnorderedContainers.h>
38#include <xrpl/basics/UptimeClock.h>
39#include <xrpl/basics/base_uint.h>
40#include <xrpl/basics/chrono.h>
41#include <xrpl/basics/contract.h>
42#include <xrpl/basics/mulDiv.h>
43#include <xrpl/basics/safe_cast.h>
44#include <xrpl/basics/scope.h>
45#include <xrpl/basics/strHex.h>
46#include <xrpl/beast/clock/abstract_clock.h>
47#include <xrpl/beast/insight/Collector.h>
48#include <xrpl/beast/insight/Gauge.h>
49#include <xrpl/beast/insight/Hook.h>
50#include <xrpl/beast/net/IPEndpoint.h>
51#include <xrpl/beast/utility/Zero.h>
52#include <xrpl/beast/utility/instrumentation.h>
53#include <xrpl/beast/utility/rngfill.h>
54#include <xrpl/config/Constants.h>
55#include <xrpl/consensus/ConsensusParms.h>
56#include <xrpl/consensus/ConsensusTypes.h>
57#include <xrpl/core/ClosureCounter.h>
58#include <xrpl/core/HashRouter.h>
59#include <xrpl/core/Job.h>
60#include <xrpl/core/NetworkIDService.h>
61#include <xrpl/core/PerfLog.h>
62#include <xrpl/core/ServiceRegistry.h>
63#include <xrpl/crypto/RFC1751.h>
64#include <xrpl/crypto/csprng.h>
65#include <xrpl/git/Git.h>
66#include <xrpl/json/json_forwards.h>
67#include <xrpl/json/json_value.h>
68#include <xrpl/json/json_writer.h>
69#include <xrpl/ledger/AcceptedLedgerTx.h>
70#include <xrpl/ledger/AmendmentTable.h>
71#include <xrpl/ledger/ApplyView.h>
72#include <xrpl/ledger/CanonicalTXSet.h>
73#include <xrpl/ledger/Ledger.h>
74#include <xrpl/ledger/OpenView.h>
75#include <xrpl/ledger/OrderBookDB.h>
76#include <xrpl/ledger/ReadView.h>
77#include <xrpl/ledger/helpers/AccountRootHelpers.h>
78#include <xrpl/ledger/helpers/DirectoryHelpers.h>
79#include <xrpl/ledger/helpers/TokenHelpers.h>
80#include <xrpl/protocol/AccountID.h>
81#include <xrpl/protocol/ApiVersion.h>
82#include <xrpl/protocol/Book.h>
83#include <xrpl/protocol/BuildInfo.h>
84#include <xrpl/protocol/ErrorCodes.h>
85#include <xrpl/protocol/Feature.h>
86#include <xrpl/protocol/Fees.h>
87#include <xrpl/protocol/Indexes.h>
88#include <xrpl/protocol/KeyType.h>
89#include <xrpl/protocol/LedgerFormats.h>
90#include <xrpl/protocol/MultiApiJson.h>
91#include <xrpl/protocol/NFTSyntheticSerializer.h>
92#include <xrpl/protocol/Protocol.h>
93#include <xrpl/protocol/PublicKey.h>
94#include <xrpl/protocol/RPCErr.h>
95#include <xrpl/protocol/Rate.h>
96#include <xrpl/protocol/SField.h>
97#include <xrpl/protocol/STAmount.h>
98#include <xrpl/protocol/STTx.h>
99#include <xrpl/protocol/SecretKey.h>
100#include <xrpl/protocol/Seed.h>
101#include <xrpl/protocol/Serializer.h>
102#include <xrpl/protocol/TER.h>
103#include <xrpl/protocol/TxFlags.h>
104#include <xrpl/protocol/TxFormats.h>
105#include <xrpl/protocol/Units.h>
106#include <xrpl/protocol/XRPAmount.h>
107#include <xrpl/protocol/jss.h>
108#include <xrpl/protocol/tokens.h>
109#include <xrpl/rdb/RelationalDatabase.h>
110#include <xrpl/resource/Fees.h>
111#include <xrpl/resource/Gossip.h>
112#include <xrpl/resource/ResourceManager.h>
113#include <xrpl/server/InfoSub.h>
114#include <xrpl/server/LoadFeeTrack.h>
115#include <xrpl/server/Manifest.h>
116#include <xrpl/shamap/SHAMap.h>
117#include <xrpl/tx/apply.h>
118
119#include <boost/asio/error.hpp>
120#include <boost/asio/io_context.hpp>
121#include <boost/asio/ip/host_name.hpp>
122#include <boost/asio/steady_timer.hpp>
123#include <boost/system/detail/errc.hpp>
124#include <boost/system/detail/error_code.hpp>
125#include <boost/system/system_error.hpp>
126
127#include <xrpl.pb.h>
128
129#include <algorithm>
130#include <array>
131#include <atomic>
132#include <chrono>
133#include <climits>
134#include <condition_variable>
135#include <cstddef>
136#include <cstdint>
137#include <cstdlib>
138#include <exception>
139#include <functional>
140#include <iterator>
141#include <limits>
142#include <memory>
143#include <mutex>
144#include <optional>
145#include <set>
146#include <sstream>
147#include <stdexcept>
148#include <string>
149#include <string_view>
150#include <type_traits>
151#include <unordered_map>
152#include <utility>
153#include <vector>
154
155namespace xrpl {
156
163class NetworkOPsImp final : public NetworkOPs
164{
168
170 {
171 public:
173 bool const admin;
174 bool const local;
176 bool applied = false;
178
180 : transaction(std::move(t)), admin(a), local(l), failType(f)
181 {
182 XRPL_ASSERT(
184 "xrpl::NetworkOPsImp::TransactionStatus::TransactionStatus : "
185 "valid inputs");
186 }
187 };
188
192 enum class DispatchState : unsigned char {
196 };
197
199
215 {
223
227 std::chrono::steady_clock::time_point start_ = std::chrono::steady_clock::now();
228 std::chrono::steady_clock::time_point const processStart_ = start_;
231
232 public:
234 {
235 counters_[static_cast<std::size_t>(OperatingMode::DISCONNECTED)].transitions = 1;
236 }
237
244 void
246
252 void
253 json(json::Value& obj) const;
254
262
263 CounterData
265 {
266 std::scoped_lock const lock(mutex_);
267 return {
268 .counters = counters_,
269 .mode = mode_,
270 .start = start_,
271 .initialSyncUs = initialSyncUs_};
272 }
273 };
274
279 {
280 ServerFeeSummary() = default;
281
283 XRPAmount fee,
284 TxQ::Metrics escalationMetrics, // trivially copyable
285 LoadFeeTrack const& loadFeeTrack);
286 bool
287 operator!=(ServerFeeSummary const& b) const;
288
289 bool
291 {
292 return !(*this != b);
293 }
294
299 };
300
301public:
303 ServiceRegistry& registry,
305 bool standalone,
306 std::size_t minPeerCount,
307 bool startValid,
308 JobQueue& jobQueue,
309 LedgerMaster& ledgerMaster,
310 ValidatorKeys const& validatorKeys,
311 boost::asio::io_context& ioCtx,
313 beast::insight::Collector::ptr const& collector)
314 : registry_(registry)
318 , heartbeatTimer_(ioCtx)
319 , clusterTimer_(ioCtx)
321 , consensus_(
322 registry_.get().getApp(),
324 setupFeeVote(registry_.get().getApp().config().section(Sections::kVoting)),
325 registry_.get().getJournal("FeeVote")),
326 ledgerMaster,
327 *localTX_,
328 registry.getInboundTransactions(),
329 beast::getAbstractClock<std::chrono::steady_clock>(),
330 validatorKeys,
331 registry_.get().getJournal("LedgerConsensus"))
332 , validatorPK_(
333 validatorKeys.keys ? validatorKeys.keys->publicKey : decltype(validatorPK_){})
335 validatorKeys.keys ? validatorKeys.keys->masterPublicKey
336 : decltype(validatorMasterPK_){})
337 , ledgerMaster_(ledgerMaster)
338 , jobQueue_(jobQueue)
339 , standalone_(standalone)
340 , minPeerCount_(startValid ? 0 : minPeerCount)
341 , stats_([this] { collectMetrics(); }, collector)
342 {
343 }
344
345 ~NetworkOPsImp() override
346 {
347 // This clear() is necessary to ensure the shared_ptrs in this map get
348 // destroyed NOW because the objects in this map invoke methods on this
349 // class when they are destroyed
350 rpcSubMap_.clear();
351 }
352
353public:
355 getOperatingMode() const override;
356
358 strOperatingMode(OperatingMode const mode, bool const admin) const override;
359
361 strOperatingMode(bool const admin = false) const override;
362
363 //
364 // Transaction operations.
365 //
366
367 // Must complete immediately.
368 void
370
371 void
373 std::shared_ptr<Transaction>& transaction,
374 bool bUnlimited,
375 bool bLocal,
376 FailHard failType) override;
377
378 void
380
389 void
390 doTransactionSync(std::shared_ptr<Transaction> transaction, bool bUnlimited, FailHard failType);
391
401 void
404 bool bUnlimited,
405 FailHard failtype);
406
407private:
408 bool
410
411 void
414 std::function<bool(std::unique_lock<std::mutex> const&)> retryCallback);
415
416public:
420 void
422
428 void
430
431 //
432 // Owner functions.
433 //
434
436 getOwnerInfo(std::shared_ptr<ReadView const> lpLedger, AccountID const& account) override;
437
438 //
439 // Book functions.
440 //
441
442 void
445 Book const&,
446 AccountID const& uTakerID,
447 bool const bProof,
448 unsigned int iLimit,
449 json::Value const& jvMarker,
450 json::Value& jvResult) override;
451
452 // Ledger proposal/close functions.
453 bool
454 processTrustedProposal(RCLCxPeerPos proposal) override;
455
456 bool
457 recvValidation(std::shared_ptr<STValidation> const& val, std::string const& source) override;
458
459 void
460 mapComplete(std::shared_ptr<SHAMap> const& map, bool fromAcquire) override;
461
462 // Network state machine.
463
464 // Used for the "jump" case.
465private:
466 void
468 bool
470
471public:
472 bool
473 beginConsensus(uint256 const& networkClosed, std::unique_ptr<std::stringstream> const& clog)
474 override;
475 void
477 void
478 setStandAlone() override;
479
484 void
485 setStateTimer() override;
486
487 void
488 setNeedNetworkLedger() override;
489 void
490 clearNeedNetworkLedger() override;
491 bool
492 isNeedNetworkLedger() override;
493 bool
494 isFull() override;
495
496 void
497 setMode(OperatingMode om) override;
498
499 bool
500 isBlocked() override;
501 bool
502 isAmendmentBlocked() override;
503 void
504 setAmendmentBlocked() override;
505 bool
506 isAmendmentWarned() override;
507 void
508 setAmendmentWarned() override;
509 void
510 clearAmendmentWarned() override;
511 bool
512 isUNLBlocked() override;
513 void
514 setUNLBlocked() override;
515 void
516 clearUNLBlocked() override;
517 void
518 consensusViewChange() override;
519
521 getConsensusInfo() override;
523 getServerInfo(bool human, bool admin, bool counters) override;
524 void
525 clearLedgerFetch() override;
527 getLedgerFetchInfo() override;
530 void
531 reportFeeChange() override;
532 void
534
535 void
536 updateLocalTx(ReadView const& view) override;
538 getLocalTxCount() override;
540 getBookSubscribersCount() override;
541
542 //
543 // Monitoring: publisher side.
544 //
545 void
546 pubLedger(std::shared_ptr<ReadView const> const& lpAccepted) override;
547 void
550 std::shared_ptr<STTx const> const& transaction,
551 TER result) override;
552 void
553 pubValidation(std::shared_ptr<STValidation> const& val) override;
554
555 //--------------------------------------------------------------------------
556 //
557 // InfoSub::Source.
558 //
559 void
560 subAccount(InfoSub::ref ispListener, hash_set<AccountID> const& vnaAccountIDs, bool rt)
561 override;
562 void
563 unsubAccount(InfoSub::ref ispListener, hash_set<AccountID> const& vnaAccountIDs, bool rt)
564 override;
565
566 // Just remove the subscription from the tracking
567 // not from the InfoSub. Needed for InfoSub destruction
568 void
569 unsubAccountInternal(std::uint64_t seq, hash_set<AccountID> const& vnaAccountIDs, bool rt)
570 override;
571
573 subAccountHistory(InfoSub::ref ispListener, AccountID const& account) override;
574 void
575 unsubAccountHistory(InfoSub::ref ispListener, AccountID const& account, bool historyOnly)
576 override;
577
578 void
579 unsubAccountHistoryInternal(std::uint64_t seq, AccountID const& account, bool historyOnly)
580 override;
581
582 void
584 std::uint64_t seq,
585 hash_set<AccountID> rtAccounts,
586 hash_set<AccountID> normalAccounts,
587 hash_set<AccountID> historyAccounts) override;
588
589 bool
590 subLedger(InfoSub::ref ispListener, json::Value& jvResult) override;
591 bool
592 unsubLedger(std::uint64_t uListener) override;
593
594 bool
595 subBookChanges(InfoSub::ref ispListener) override;
596 bool
597 unsubBookChanges(std::uint64_t uListener) override;
598
599 bool
600 subServer(InfoSub::ref ispListener, json::Value& jvResult, bool admin) override;
601 bool
602 unsubServer(std::uint64_t uListener) override;
603
604 bool
605 subBook(InfoSub::ref ispListener, Book const&) override;
606 bool
607 unsubBook(InfoSub::ref ispListener, Book const&) override;
608 bool
609 unsubBookInternal(std::uint64_t uListener, Book const&) override;
610
611 bool
612 subManifests(InfoSub::ref ispListener) override;
613 bool
614 unsubManifests(std::uint64_t uListener) override;
615 void
616 pubManifest(Manifest const&) override;
617
618 bool
619 subTransactions(InfoSub::ref ispListener) override;
620 bool
621 unsubTransactions(std::uint64_t uListener) override;
622
623 bool
624 subRTTransactions(InfoSub::ref ispListener) override;
625 bool
626 unsubRTTransactions(std::uint64_t uListener) override;
627
628 bool
629 subValidations(InfoSub::ref ispListener) override;
630 bool
631 unsubValidations(std::uint64_t uListener) override;
632
633 bool
634 subPeerStatus(InfoSub::ref ispListener) override;
635 bool
636 unsubPeerStatus(std::uint64_t uListener) override;
637 void
638 pubPeerStatus(std::function<json::Value(void)> const&) override;
639
640 bool
641 subConsensus(InfoSub::ref ispListener) override;
642 bool
643 unsubConsensus(std::uint64_t uListener) override;
644
646 findRpcSub(std::string const& strUrl) override;
648 addRpcSub(std::string const& strUrl, InfoSub::ref) override;
649 bool
650 tryRemoveRpcSub(std::string const& strUrl) override;
651
664 findRpcSubLocked(std::string const& strUrl);
665
666 beast::Journal const&
667 journal() const override
668 {
669 return journal_;
670 }
671
672 void
673 stop() override
674 {
675 {
676 try
677 {
678 heartbeatTimer_.cancel();
679 }
680 catch (boost::system::system_error const& e)
681 {
682 JLOG(journal_.error()) << "NetworkOPs: heartbeatTimer cancel error: " << e.what();
683 }
684
685 try
686 {
687 clusterTimer_.cancel();
688 }
689 catch (boost::system::system_error const& e)
690 {
691 JLOG(journal_.error()) << "NetworkOPs: clusterTimer cancel error: " << e.what();
692 }
693
694 try
695 {
696 accountHistoryTxTimer_.cancel();
697 }
698 catch (boost::system::system_error const& e)
699 {
700 JLOG(journal_.error())
701 << "NetworkOPs: accountHistoryTxTimer cancel error: " << e.what();
702 }
703 }
704 // Make sure that any waitHandlers pending in our timers are done.
705 using namespace std::chrono_literals;
706 waitHandlerCounter_.join("NetworkOPs", 1s, journal_);
707 }
708
709 void
710 stateAccounting(json::Value& obj) override;
711
712private:
713 void
714 setTimer(
715 boost::asio::steady_timer& timer,
716 std::chrono::milliseconds const& expiryTime,
717 std::function<void()> onExpire,
718 std::function<void()> onError);
719 void
721 void
723 void
725 void
727
729 transJson(
730 std::shared_ptr<STTx const> const& transaction,
731 TER result,
732 bool validated,
735
736 void
739 AcceptedLedgerTx const& transaction,
740 bool last);
741
742 void
745 AcceptedLedgerTx const& transaction,
746 bool last);
747
771 void
772 pubBookTransaction(AcceptedLedgerTx const& transaction, MultiApiJson const& jvObj);
773
774 void
777 std::shared_ptr<STTx const> const& transaction,
778 TER result);
779
784 void
786 std::shared_ptr<ReadView const> const& lpAccepted,
787 std::shared_ptr<AcceptedLedger const> const& alpAccepted);
788
794 void
796
797 void
798 pubServer();
799 void
801
803 getHostId(bool forAdmin);
804
805private:
809
810 /*
811 * With a validated ledger to separate history and future, the node
812 * streams historical txns with negative indexes starting from -1,
813 * and streams future txns starting from index 0.
814 * The SubAccountHistoryIndex struct maintains these indexes.
815 * It also has a flag stopHistorical_ for stopping streaming
816 * the historical txns.
817 */
847
853 void
857 void
859 void
861
872 static constexpr std::size_t kAccountCleanupChunk = 4096;
873
895 template <typename OuterMap, typename BeforeErase>
896 void
898 std::uint64_t seq,
899 hash_set<AccountID> const& accounts,
900 OuterMap& outerMap,
901 BeforeErase&& beforeErase);
902
910 void
912 std::uint64_t seq,
913 hash_set<AccountID> const& accounts,
914 SubInfoMapType& subMap);
915
920 void
922
925
927
928 // Independent lock domains so a long cleanup/publish on one does not stall
929 // the others. Hold at most one at a time; if ever more, order: accountLock_,
930 // bookLock_, streamLock_.
931 //
932 // Deferred-destruction rule (non-recursive mutexes): under bookLock_ or
933 // streamLock_, never let the last InfoSub pointer die inside the lock -
934 // ~InfoSub re-acquires it via unsub* -> self-deadlock. Publishers collect the
935 // locked pointers in a vector declared before the lock and destruct after
936 // release (see pubServer / pubBookTransaction). accountLock_ is exempt:
937 // ~InfoSub offloads account teardown to scheduleAccountCleanup.
941
943
948
950 boost::asio::steady_timer heartbeatTimer_;
951 boost::asio::steady_timer clusterTimer_;
952 boost::asio::steady_timer accountHistoryTxTimer_;
953
955
958
960
962
973
977
979
981
982 // Used as array indices; converting to enum class would require casts at ~40 call sites.
983 // NOLINTNEXTLINE(cppcoreguidelines-use-enum-class)
984 enum SubTypes {
985 SLedger, // Accepted ledgers.
986 SManifests, // Received validator manifests.
987 SServer, // When server changes connectivity state.
988 STransactions, // All accepted transactions.
989 SRtTransactions, // All proposed and accepted transactions.
990 SValidations, // Received validations.
991 SPeerStatus, // Peer status changes.
992 SConsensusPhase, // Consensus phase
993 SBookChanges, // Per-ledger order book changes
994 SLastEntry // Any new entry must be ADDED ABOVE this one
995 };
996
1002
1004
1006
1007 // Whether we are in standalone mode.
1008 bool const standalone_;
1009
1010 // The number of nodes that we need to consider ourselves connected.
1012
1013 // Transaction batching.
1018
1020
1023
1024private:
1025 struct Stats
1026 {
1027 template <class Handler>
1028 Stats(Handler const& handler, beast::insight::Collector::ptr const& collector)
1029 : hook(collector->makeHook(handler))
1031 collector->makeGauge("State_Accounting", "Disconnected_duration"))
1032 , connectedDuration(collector->makeGauge("State_Accounting", "Connected_duration"))
1033 , syncingDuration(collector->makeGauge("State_Accounting", "Syncing_duration"))
1034 , trackingDuration(collector->makeGauge("State_Accounting", "Tracking_duration"))
1035 , fullDuration(collector->makeGauge("State_Accounting", "Full_duration"))
1037 collector->makeGauge("State_Accounting", "Disconnected_transitions"))
1039 collector->makeGauge("State_Accounting", "Connected_transitions"))
1040 , syncingTransitions(collector->makeGauge("State_Accounting", "Syncing_transitions"))
1041 , trackingTransitions(collector->makeGauge("State_Accounting", "Tracking_transitions"))
1042 , fullTransitions(collector->makeGauge("State_Accounting", "Full_transitions"))
1043 {
1044 }
1045
1052
1058 };
1059
1060 std::mutex statsMutex_; // Mutex to lock stats_
1062
1063private:
1064 void
1066};
1067
1068//------------------------------------------------------------------------------
1069
1071 {"disconnected", "connected", "syncing", "tracking", "full"}};
1072
1074
1081
1082static auto const kGenesisAccountId =
1083 calcAccountID(generateKeyPair(KeyType::Secp256k1, generateSeed("masterpassphrase")).first);
1084
1085//------------------------------------------------------------------------------
1086inline OperatingMode
1088{
1089 return mode_;
1090}
1091
1092inline std::string
1093NetworkOPsImp::strOperatingMode(bool const admin /* = false */) const
1094{
1095 return strOperatingMode(mode_, admin);
1096}
1097
1098inline void
1103
1104inline void
1109
1110inline void
1115
1116inline bool
1121
1122inline bool
1127
1130{
1131 static std::string const kHostname = boost::asio::ip::host_name();
1132
1133 if (forAdmin)
1134 return kHostname;
1135
1136 // For non-admin uses hash the node public key into a
1137 // single RFC1751 word:
1138 static std::string const kShroudedHostId = [this]() {
1139 auto const& id = registry_.get().getApp().nodeIdentity();
1140
1141 return RFC1751::getWordFromBlob(id.first.data(), id.first.size());
1142 }();
1143
1144 return kShroudedHostId;
1145}
1146
1147void
1149{
1151
1152 // Only do this work if a cluster is configured
1153 if (registry_.get().getCluster().size() != 0)
1155}
1156
1157void
1159 boost::asio::steady_timer& timer,
1160 std::chrono::milliseconds const& expiryTime,
1161 std::function<void()> onExpire,
1162 std::function<void()> onError)
1163{
1164 // Only start the timer if waitHandlerCounter_ is not yet joined.
1165 if (auto optionalCountedHandler =
1166 waitHandlerCounter_.wrap([this, onExpire, onError](boost::system::error_code const& e) {
1167 if ((e.value() == boost::system::errc::success) && (!jobQueue_.isStopped()))
1168 {
1169 onExpire();
1170 }
1171 // Recover as best we can if an unexpected error occurs.
1172 if (e.value() != boost::system::errc::success &&
1173 e.value() != boost::asio::error::operation_aborted)
1174 {
1175 // Try again later and hope for the best.
1176 JLOG(journal_.error())
1177 << "Timer got error '" << e.message() << "'. Restarting timer.";
1178 onError();
1179 }
1180 }))
1181 {
1182 timer.expires_after(expiryTime);
1183 timer.async_wait(std::move(*optionalCountedHandler));
1184 }
1185}
1186
1187void
1189{
1190 setTimer(
1192 consensus_.parms().ledgerGRANULARITY,
1193 [this]() {
1194 jobQueue_.addJob(JtNetopTimer, "NetHeart", [this]() { processHeartbeatTimer(); });
1195 },
1196 [this]() { setHeartbeatTimer(); });
1197}
1198
1199void
1201{
1202 using namespace std::chrono_literals;
1203
1204 setTimer(
1206 10s,
1207 [this]() {
1208 jobQueue_.addJob(JtNetopCluster, "NetCluster", [this]() { processClusterTimer(); });
1209 },
1210 [this]() { setClusterTimer(); });
1211}
1212
1213void
1215{
1216 JLOG(journal_.debug()) << "Scheduling AccountHistory job for account "
1217 << toBase58(subInfo.index->accountId);
1218 using namespace std::chrono_literals;
1219 setTimer(
1221 4s,
1222 [this, subInfo]() { addAccountHistoryJob(subInfo); },
1223 [this, subInfo]() { setAccountHistoryJobTimer(subInfo); });
1224}
1225
1226void
1228{
1229 RclConsensusLogger clog("Heartbeat Timer", consensus_.validating(), journal_);
1230 {
1231 std::unique_lock lock{registry_.get().getApp().getMasterMutex()};
1232
1233 // VFALCO NOTE This is for diagnosing a crash on exit
1234 LoadManager& mgr(registry_.get().getLoadManager());
1235 mgr.heartbeat();
1236
1237 std::size_t const numPeers = registry_.get().getOverlay().size();
1238
1239 // do we have sufficient peers? If not, we are disconnected.
1240 if (numPeers < minPeerCount_)
1241 {
1243 {
1246 ss << "Node count (" << numPeers << ") has fallen "
1247 << "below required minimum (" << minPeerCount_ << ").";
1248 JLOG(journal_.warn()) << ss.str();
1249 CLOG(clog.ss()) << "set mode to DISCONNECTED: " << ss.str();
1250 }
1251 else
1252 {
1253 CLOG(clog.ss()) << "already DISCONNECTED. too few peers (" << numPeers
1254 << "), need at least " << minPeerCount_;
1255 }
1256
1257 // MasterMutex lock need not be held to call setHeartbeatTimer()
1258 lock.unlock();
1259 // We do not call consensus_.timerEntry until there are enough
1260 // peers providing meaningful inputs to consensus
1262
1263 return;
1264 }
1265
1267 {
1269 JLOG(journal_.info()) << "Node count (" << numPeers << ") is sufficient.";
1270 CLOG(clog.ss()) << "setting mode to CONNECTED based on " << numPeers << " peers. ";
1271 }
1272
1273 // Check if the last validated ledger forces a change between these
1274 // states.
1275 auto origMode = mode_.load();
1276 CLOG(clog.ss()) << "mode: " << strOperatingMode(origMode, true);
1278 {
1280 }
1281 else if (mode_ == OperatingMode::CONNECTED)
1282 {
1284 }
1285 auto newMode = mode_.load();
1286 if (origMode != newMode)
1287 {
1288 CLOG(clog.ss()) << ", changing to " << strOperatingMode(newMode, true);
1289 }
1290 CLOG(clog.ss()) << ". ";
1291 }
1292
1293 consensus_.timerEntry(registry_.get().getTimeKeeper().closeTime(), clog.ss());
1294
1295 CLOG(clog.ss()) << "consensus phase " << to_string(lastConsensusPhase_);
1296 ConsensusPhase const currPhase = consensus_.phase();
1297 if (lastConsensusPhase_ != currPhase)
1298 {
1299 reportConsensusStateChange(currPhase);
1300 lastConsensusPhase_ = currPhase;
1301 CLOG(clog.ss()) << " changed to " << to_string(lastConsensusPhase_);
1302 }
1303 CLOG(clog.ss()) << ". ";
1304
1306}
1307
1308void
1310{
1311 if (registry_.get().getCluster().size() == 0)
1312 return;
1313
1314 using namespace std::chrono_literals;
1315
1316 bool const update = registry_.get().getCluster().update(
1317 registry_.get().getApp().nodeIdentity().first,
1318 "",
1319 (ledgerMaster_.getValidatedLedgerAge() <= 4min)
1320 ? registry_.get().getFeeTrack().getLocalFee()
1321 : 0,
1322 registry_.get().getTimeKeeper().now());
1323
1324 if (!update)
1325 {
1326 JLOG(journal_.debug()) << "Too soon to send cluster update";
1328 return;
1329 }
1330
1331 protocol::TMCluster cluster;
1332 registry_.get().getCluster().forEach([&cluster](ClusterNode const& node) {
1333 protocol::TMClusterNode& n = *cluster.add_clusternodes();
1334 n.set_publickey(toBase58(TokenType::NodePublic, node.identity()));
1335 n.set_reporttime(node.getReportTime().time_since_epoch().count());
1336 n.set_nodeload(node.getLoadFee());
1337 if (!node.name().empty())
1338 n.set_nodename(node.name());
1339 });
1340
1341 resource::Gossip const gossip = registry_.get().getResourceManager().exportConsumers();
1342 for (auto& item : gossip.items)
1343 {
1344 protocol::TMLoadSource& node = *cluster.add_loadsources();
1345 node.set_name(to_string(item.address));
1346 node.set_cost(item.balance);
1347 }
1348 registry_.get().getOverlay().foreach(
1349 sendIf(std::make_shared<Message>(cluster, protocol::mtCLUSTER), PeerInCluster()));
1351}
1352
1353//------------------------------------------------------------------------------
1354
1356NetworkOPsImp::strOperatingMode(OperatingMode const mode, bool const admin) const
1357{
1358 if (mode == OperatingMode::FULL && admin)
1359 {
1360 auto const consensusMode = consensus_.mode();
1361 if (consensusMode != ConsensusMode::WrongLedger)
1362 {
1363 if (consensusMode == ConsensusMode::Proposing)
1364 return "proposing";
1365
1366 if (consensus_.validating())
1367 return "validating";
1368 }
1369 }
1370
1371 return kStates[static_cast<std::size_t>(mode)];
1372}
1373
1374void
1376{
1377 if (isNeedNetworkLedger())
1378 {
1379 // Nothing we can do if we've never been in sync
1380 return;
1381 }
1382
1383 // Reject any transaction with the tfInnerBatchTxn flag at the network
1384 // boundary, regardless of amendment state.
1385 if (iTrans->isFlag(tfInnerBatchTxn))
1386 {
1387 JLOG(journal_.error()) << "Submitted transaction invalid: tfInnerBatchTxn flag present.";
1388 return;
1389 }
1390
1391 // this is an asynchronous interface
1392 auto const trans = sterilize(*iTrans);
1393
1394 auto const txid = trans->getTransactionID();
1395 auto const flags = registry_.get().getHashRouter().getFlags(txid);
1396
1398 {
1399 JLOG(journal_.warn()) << "Submitted transaction cached bad";
1400 return;
1401 }
1402
1403 try
1404 {
1405 auto const [validity, reason] = checkValidity(
1406 registry_.get().getHashRouter(), *trans, ledgerMaster_.getValidatedRules());
1407
1408 if (validity != Validity::Valid)
1409 {
1410 JLOG(journal_.warn()) << "Submitted transaction invalid: " << reason;
1411 return;
1412 }
1413 }
1414 catch (std::exception const& ex)
1415 {
1416 JLOG(journal_.warn()) << "Exception checking transaction " << txid << ": " << ex.what();
1417
1418 return;
1419 }
1420
1421 std::string reason;
1422
1423 auto tx = std::make_shared<Transaction>(trans, reason, registry_.get().getApp());
1424
1425 jobQueue_.addJob(JtTransaction, "SubmitTxn", [this, tx]() {
1426 auto t = tx;
1427 processTransaction(t, false, false, FailHard::No);
1428 });
1429}
1430
1431bool
1433{
1434 auto const newFlags = registry_.get().getHashRouter().getFlags(transaction->getID());
1435
1437 {
1438 // cached bad
1439 JLOG(journal_.warn()) << transaction->getID() << ": cached bad!\n";
1440 transaction->setStatus(TransStatus::INVALID);
1441 transaction->setResult(temBAD_SIGNATURE);
1442 return false;
1443 }
1444
1445 auto const view = ledgerMaster_.getCurrentLedger();
1446
1447 // This function is called by several different parts of the codebase
1448 // under no circumstances will we ever accept an inner txn within a batch
1449 // txn from the network.
1450 auto const sttx = *transaction->getSTransaction();
1451 if (sttx.isFlag(tfInnerBatchTxn))
1452 {
1453 transaction->setStatus(TransStatus::INVALID);
1454 transaction->setResult(temINVALID_FLAG);
1455 registry_.get().getHashRouter().setFlags(transaction->getID(), HashRouterFlags::BAD);
1456 return false;
1457 }
1458
1459 // NOTE ximinez - I think this check is redundant,
1460 // but I'm not 100% sure yet.
1461 // If so, only cost is looking up HashRouter flags.
1462 auto const [validity, reason] =
1463 checkValidity(registry_.get().getHashRouter(), sttx, view->rules());
1464 XRPL_ASSERT(
1465 validity == Validity::Valid, "xrpl::NetworkOPsImp::processTransaction : valid validity");
1466
1467 // Not concerned with local checks at this point.
1468 if (validity == Validity::SigBad)
1469 {
1470 JLOG(journal_.info()) << "Transaction has bad signature: " << reason;
1471 transaction->setStatus(TransStatus::INVALID);
1472 transaction->setResult(temBAD_SIGNATURE);
1473 registry_.get().getHashRouter().setFlags(transaction->getID(), HashRouterFlags::BAD);
1474 return false;
1475 }
1476
1477 // canonicalize can change our pointer
1478 registry_.get().getMasterTransaction().canonicalize(&transaction);
1479
1480 return true;
1481}
1482
1483void
1485 std::shared_ptr<Transaction>& transaction,
1486 bool bUnlimited,
1487 bool bLocal,
1488 FailHard failType)
1489{
1490 auto ev = jobQueue_.makeLoadEvent(JtTxnProc, "ProcessTXN");
1491
1492 // preProcessTransaction can change our pointer
1493 if (!preProcessTransaction(transaction))
1494 return;
1495
1496 if (bLocal)
1497 {
1498 doTransactionSync(transaction, bUnlimited, failType);
1499 }
1500 else
1501 {
1502 doTransactionAsync(transaction, bUnlimited, failType);
1503 }
1504}
1505
1506void
1508 std::shared_ptr<Transaction> transaction,
1509 bool bUnlimited,
1510 FailHard failType)
1511{
1512 std::scoped_lock const lock(mutex_);
1513
1514 if (transaction->getApplying())
1515 return;
1516
1517 transactions_.emplace_back(transaction, bUnlimited, false, failType);
1518 transaction->setApplying();
1519
1521 {
1522 if (jobQueue_.addJob(JtBatch, "TxBatchAsync", [this]() { transactionBatch(); }))
1523 {
1525 }
1526 }
1527}
1528
1529void
1531 std::shared_ptr<Transaction> transaction,
1532 bool bUnlimited,
1533 FailHard failType)
1534{
1536
1537 if (!transaction->getApplying())
1538 {
1539 transactions_.emplace_back(transaction, bUnlimited, true, failType);
1540 transaction->setApplying();
1541 }
1542
1543 doTransactionSyncBatch(lock, [&transaction](std::unique_lock<std::mutex> const&) {
1544 return transaction->getApplying();
1545 });
1546}
1547
1548void
1551 std::function<bool(std::unique_lock<std::mutex> const&)> retryCallback)
1552{
1553 do
1554 {
1556 {
1557 // A batch processing job is already running, so wait.
1558 cond_.wait(lock);
1559 }
1560 else
1561 {
1562 apply(lock);
1563
1564 if (!transactions_.empty())
1565 {
1566 // More transactions need to be applied, but by another job.
1567 if (jobQueue_.addJob(JtBatch, "TxBatchSync", [this]() { transactionBatch(); }))
1568 {
1570 }
1571 }
1572 }
1573 } while (retryCallback(lock));
1574}
1575
1576void
1578{
1579 auto ev = jobQueue_.makeLoadEvent(JtTxnProc, "ProcessTXNSet");
1581 candidates.reserve(set.size());
1582 for (auto const& [_, tx] : set)
1583 {
1584 std::string reason;
1585 auto transaction = std::make_shared<Transaction>(tx, reason, registry_.get().getApp());
1586
1587 if (transaction->getStatus() == TransStatus::INVALID)
1588 {
1589 if (!reason.empty())
1590 {
1591 JLOG(journal_.trace()) << "Exception checking transaction: " << reason;
1592 }
1593 registry_.get().getHashRouter().setFlags(tx->getTransactionID(), HashRouterFlags::BAD);
1594 continue;
1595 }
1596
1597 // preProcessTransaction can change our pointer
1598 if (!preProcessTransaction(transaction))
1599 continue;
1600
1601 candidates.emplace_back(transaction);
1602 }
1603
1605 transactions.reserve(candidates.size());
1606
1608
1609 for (auto& transaction : candidates)
1610 {
1611 if (!transaction->getApplying())
1612 {
1613 transactions.emplace_back(transaction, false, false, FailHard::No);
1614 transaction->setApplying();
1615 }
1616 }
1617
1618 if (transactions_.empty())
1619 {
1621 }
1622 else
1623 {
1624 transactions_.reserve(transactions_.size() + transactions.size());
1625 for (auto& t : transactions)
1626 transactions_.push_back(std::move(t));
1627 }
1628 if (transactions_.empty())
1629 {
1630 JLOG(journal_.debug()) << "No transaction to process!";
1631 return;
1632 }
1633
1635 XRPL_ASSERT(lock.owns_lock(), "xrpl::NetworkOPsImp::processTransactionSet has lock");
1636 return std::ranges::any_of(
1637 transactions_, [](auto const& t) { return t.transaction->getApplying(); });
1638 });
1639}
1640
1641void
1643{
1645
1647 return;
1648
1649 while (!transactions_.empty())
1650 {
1651 apply(lock);
1652 }
1653}
1654
1655void
1657{
1661 XRPL_ASSERT(!transactions.empty(), "xrpl::NetworkOPsImp::apply : non-empty transactions");
1662 XRPL_ASSERT(
1663 dispatchState_ != DispatchState::Running, "xrpl::NetworkOPsImp::apply : is not running");
1664
1666
1667 batchLock.unlock();
1668
1669 {
1670 std::unique_lock masterLock{registry_.get().getApp().getMasterMutex(), std::defer_lock};
1671 bool changed = false;
1672 {
1673 std::unique_lock ledgerLock{ledgerMaster_.peekMutex(), std::defer_lock};
1674 std::lock(masterLock, ledgerLock);
1675 registry_.get().getOpenLedger().modify([&](OpenView& view, beast::Journal j) {
1677 {
1678 // we check before adding to the batch
1679 ApplyFlags flags = TapNone;
1680 if (e.admin)
1681 flags |= TapUnlimited;
1682
1683 if (e.failType == FailHard::Yes)
1684 flags |= TapFailHard;
1685
1686 auto const result = registry_.get().getTxQ().apply(
1687 registry_.get().getApp(), view, e.transaction->getSTransaction(), flags, j);
1688 e.result = result.ter;
1689 e.applied = result.applied;
1690 changed = changed || result.applied;
1691 }
1692 return changed;
1693 });
1694 }
1695 if (changed)
1697
1698 std::optional<LedgerIndex> validatedLedgerIndex;
1699 if (auto const l = ledgerMaster_.getValidatedLedger())
1700 validatedLedgerIndex = l->header().seq;
1701
1702 auto newOL = registry_.get().getOpenLedger().current();
1703 for (TransactionStatus const& e : transactions)
1704 {
1705 e.transaction->clearSubmitResult();
1706
1707 if (e.applied)
1708 {
1709 pubProposedTransaction(newOL, e.transaction->getSTransaction(), e.result);
1710 e.transaction->setApplied();
1711 }
1712
1713 e.transaction->setResult(e.result);
1714
1715 if (isTemMalformed(e.result))
1716 {
1717 registry_.get().getHashRouter().setFlags(
1718 e.transaction->getID(), HashRouterFlags::BAD);
1719 }
1720
1721#ifdef DEBUG
1722 if (!isTesSuccess(e.result))
1723 {
1724 std::string token, human;
1725
1726 if (transResultInfo(e.result, token, human))
1727 {
1728 JLOG(journal_.info()) << "TransactionResult: " << token << ": " << human;
1729 }
1730 }
1731#endif
1732
1733 bool const addLocal = e.local;
1734
1735 if (isTesSuccess(e.result))
1736 {
1737 JLOG(journal_.debug()) << "Transaction is now included in open ledger";
1738 e.transaction->setStatus(TransStatus::INCLUDED);
1739
1740 // Pop as many "reasonable" transactions for this account as
1741 // possible. "Reasonable" means they have sequential sequence
1742 // numbers, or use tickets.
1743 auto const& txCur = e.transaction->getSTransaction();
1744
1745 std::size_t count = 0;
1746 for (auto txNext = ledgerMaster_.popAcctTransaction(txCur);
1747 txNext && count < kMaxPoppedTransactions;
1748 txNext = ledgerMaster_.popAcctTransaction(txCur), ++count)
1749 {
1750 if (!batchLock.owns_lock())
1751 batchLock.lock();
1752 std::string reason;
1753 auto const trans = sterilize(*txNext);
1754 auto t = std::make_shared<Transaction>(trans, reason, registry_.get().getApp());
1755 if (t->getApplying())
1756 break;
1757 submitHeld.emplace_back(t, false, false, FailHard::No);
1758 t->setApplying();
1759 }
1760 if (batchLock.owns_lock())
1761 batchLock.unlock();
1762 }
1763 else if (e.result == tefPAST_SEQ)
1764 {
1765 // duplicate or conflict
1766 JLOG(journal_.info()) << "Transaction is obsolete";
1767 e.transaction->setStatus(TransStatus::OBSOLETE);
1768 }
1769 else if (e.result == terQUEUED)
1770 {
1771 JLOG(journal_.debug()) << "Transaction is likely to claim a"
1772 << " fee, but is queued until fee drops";
1773
1774 e.transaction->setStatus(TransStatus::HELD);
1775 // Add to held transactions, because it could get
1776 // kicked out of the queue, and this will try to
1777 // put it back.
1778 ledgerMaster_.addHeldTransaction(e.transaction);
1779 e.transaction->setQueued();
1780 e.transaction->setKept();
1781 }
1782 else if (isTerRetry(e.result) || isTelLocal(e.result) || isTefFailure(e.result))
1783 {
1784 if (e.failType != FailHard::Yes)
1785 {
1786 auto const lastLedgerSeq =
1787 e.transaction->getSTransaction()->at(~sfLastLedgerSequence);
1788 auto const ledgersLeft = lastLedgerSeq
1789 ? *lastLedgerSeq - ledgerMaster_.getCurrentLedgerIndex()
1791 // If any of these conditions are met, the transaction can
1792 // be held:
1793 // 1. It was submitted locally. (Note that this flag is only
1794 // true on the initial submission.)
1795 // 2. The transaction has a LastLedgerSequence, and the
1796 // LastLedgerSequence is fewer than LocalTxs::kHoldLedgers
1797 // (5) ledgers into the future. (Remember that an
1798 // unseated optional compares as less than all seated
1799 // values, so it has to be checked explicitly first.)
1800 // 3. The HashRouterFlags::BAD flag is not set on the txID.
1801 // (setFlags
1802 // checks before setting. If the flag is set, it returns
1803 // false, which means it's been held once without one of
1804 // the other conditions, so don't hold it again. Time's
1805 // up!)
1806 //
1807 if (e.local || (ledgersLeft && ledgersLeft <= LocalTxs::kHoldLedgers) ||
1808 registry_.get().getHashRouter().setFlags(
1809 e.transaction->getID(), HashRouterFlags::HELD))
1810 {
1811 // transaction should be held
1812 JLOG(journal_.debug()) << "Transaction should be held: " << e.result;
1813 e.transaction->setStatus(TransStatus::HELD);
1814 ledgerMaster_.addHeldTransaction(e.transaction);
1815 e.transaction->setKept();
1816 }
1817 else
1818 {
1819 JLOG(journal_.debug())
1820 << "Not holding transaction " << e.transaction->getID() << ": "
1821 << (e.local ? "local" : "network") << ", "
1822 << "result: " << e.result << " ledgers left: "
1823 << (ledgersLeft ? to_string(*ledgersLeft) : "unspecified");
1824 }
1825 }
1826 }
1827 else
1828 {
1829 JLOG(journal_.debug()) << "Status other than success " << e.result;
1830 e.transaction->setStatus(TransStatus::INVALID);
1831 }
1832
1833 auto const enforceFailHard = e.failType == FailHard::Yes && !isTesSuccess(e.result);
1834
1835 if (addLocal && !enforceFailHard)
1836 {
1837 localTX_->pushBack(
1838 ledgerMaster_.getCurrentLedgerIndex(), e.transaction->getSTransaction());
1839 e.transaction->setKept();
1840 }
1841
1842 if ((e.applied ||
1843 ((mode_ != OperatingMode::FULL) && (e.failType != FailHard::Yes) && e.local) ||
1844 (e.result == terQUEUED)) &&
1845 !enforceFailHard)
1846 {
1847 auto const toSkip =
1848 registry_.get().getHashRouter().shouldRelay(e.transaction->getID());
1849 if (auto const sttx = *(e.transaction->getSTransaction()); toSkip &&
1850 // Skip relaying if it's an inner batch txn. The flag should
1851 // only be set if the Batch feature is enabled. If Batch is
1852 // not enabled, the flag is always invalid, so don't relay
1853 // it regardless.
1854 !(sttx.isFlag(tfInnerBatchTxn)))
1855 {
1856 protocol::TMTransaction tx;
1857 Serializer s;
1858
1859 sttx.add(s);
1860 tx.set_rawtransaction(s.data(), s.size());
1861 tx.set_status(protocol::tsCURRENT);
1862 tx.set_receivetimestamp(
1863 registry_.get().getTimeKeeper().now().time_since_epoch().count());
1864 tx.set_deferred(e.result == terQUEUED);
1865 // FIXME: This should be when we received it
1866 registry_.get().getOverlay().relay(e.transaction->getID(), tx, *toSkip);
1867 e.transaction->setBroadcast();
1868 }
1869 }
1870
1871 if (validatedLedgerIndex)
1872 {
1873 auto [fee, accountSeq, availableSeq] =
1874 registry_.get().getTxQ().getTxRequiredFeeAndSeq(
1875 *newOL, e.transaction->getSTransaction());
1876 e.transaction->setCurrentLedgerState(
1877 *validatedLedgerIndex, fee, accountSeq, availableSeq);
1878 }
1879 }
1880 }
1881
1882 batchLock.lock();
1883
1884 for (TransactionStatus const& e : transactions)
1885 e.transaction->clearApplying();
1886
1887 if (!submitHeld.empty())
1888 {
1889 if (transactions_.empty())
1890 {
1891 transactions_.swap(submitHeld);
1892 }
1893 else
1894 {
1895 transactions_.reserve(transactions_.size() + submitHeld.size());
1896 for (auto& e : submitHeld)
1897 transactions_.push_back(std::move(e));
1898 }
1899 }
1900
1901 cond_.notify_all();
1902
1904}
1905
1906//
1907// Owner functions
1908//
1909
1912{
1914 auto root = keylet::ownerDir(account);
1915 auto sleNode = lpLedger->read(keylet::page(root));
1916 if (sleNode)
1917 {
1918 std::uint64_t uNodeDir = 0;
1919
1920 do
1921 {
1922 for (auto const& uDirEntry : sleNode->getFieldV256(sfIndexes))
1923 {
1924 auto sleCur = lpLedger->read(keylet::child(uDirEntry));
1925 XRPL_ASSERT(sleCur, "xrpl::NetworkOPsImp::getOwnerInfo : non-null child SLE");
1926
1927 switch (sleCur->getType())
1928 {
1929 case ltOFFER:
1930 if (!jvObjects.isMember(jss::offers))
1931 jvObjects[jss::offers] = json::Value(json::ValueType::Array);
1932
1933 jvObjects[jss::offers].append(sleCur->getJson(JsonOptions::Values::None));
1934 break;
1935
1936 case ltRIPPLE_STATE:
1937 if (!jvObjects.isMember(jss::ripple_lines))
1938 {
1939 jvObjects[jss::ripple_lines] = json::Value(json::ValueType::Array);
1940 }
1941
1942 jvObjects[jss::ripple_lines].append(
1943 sleCur->getJson(JsonOptions::Values::None));
1944 break;
1945
1946 case ltACCOUNT_ROOT:
1947 case ltDIR_NODE:
1948 // LCOV_EXCL_START
1949 default:
1950 UNREACHABLE(
1951 "xrpl::NetworkOPsImp::getOwnerInfo : invalid "
1952 "type");
1953 break;
1954 // LCOV_EXCL_STOP
1955 }
1956 }
1957
1958 uNodeDir = sleNode->getFieldU64(sfIndexNext);
1959
1960 if (uNodeDir != 0u)
1961 {
1962 sleNode = lpLedger->read(keylet::page(root, uNodeDir));
1963 XRPL_ASSERT(sleNode, "xrpl::NetworkOPsImp::getOwnerInfo : read next page");
1964 }
1965 } while (uNodeDir != 0u);
1966 }
1967
1968 return jvObjects;
1969}
1970
1971//
1972// Other
1973//
1974
1975inline bool
1980
1981inline bool
1986
1987void
1993
1994inline bool
1999
2000inline void
2005
2006inline void
2011
2012inline bool
2014{
2015 return unlBlocked_;
2016}
2017
2018void
2024
2025inline void
2030
2031bool
2033{
2034 // Returns true if there's an *abnormal* ledger issue, normal changing in
2035 // TRACKING mode should return false. Do we have sufficient validations for
2036 // our last closed ledger? Or do sufficient nodes agree? And do we have no
2037 // better ledger available? If so, we are either tracking or full.
2038
2039 JLOG(journal_.trace()) << "NetworkOPsImp::checkLastClosedLedger";
2040
2041 auto const ourClosed = ledgerMaster_.getClosedLedger();
2042
2043 if (!ourClosed)
2044 return false;
2045
2046 uint256 closedLedger = ourClosed->header().hash;
2047 uint256 const prevClosedLedger = ourClosed->header().parentHash;
2048 JLOG(journal_.trace()) << "OurClosed: " << closedLedger;
2049 JLOG(journal_.trace()) << "PrevClosed: " << prevClosedLedger;
2050
2051 //-------------------------------------------------------------------------
2052 // Determine preferred last closed ledger
2053
2054 auto& validations = registry_.get().getValidations();
2055 JLOG(journal_.debug()) << "ValidationTrie " << json::Compact(validations.getJsonTrie());
2056
2057 // Will rely on peer LCL if no trusted validations exist
2059 peerCounts[closedLedger] = 0;
2061 peerCounts[closedLedger]++;
2062
2063 for (auto& peer : peerList)
2064 {
2065 uint256 const peerLedger = peer->getClosedLedgerHash();
2066
2067 if (peerLedger.isNonZero())
2068 ++peerCounts[peerLedger];
2069 }
2070
2071 for (auto const& it : peerCounts)
2072 JLOG(journal_.debug()) << "L: " << it.first << " n=" << it.second;
2073
2074 uint256 const preferredLCL = validations.getPreferredLCL(
2075 RCLValidatedLedger{ourClosed, validations.adaptor().journal()},
2076 ledgerMaster_.getValidLedgerIndex(),
2077 peerCounts);
2078
2079 bool switchLedgers = preferredLCL != closedLedger;
2080 if (switchLedgers)
2081 closedLedger = preferredLCL;
2082 //-------------------------------------------------------------------------
2083 if (switchLedgers && (closedLedger == prevClosedLedger))
2084 {
2085 // don't switch to our own previous ledger
2086 JLOG(journal_.info()) << "We won't switch to our own previous ledger";
2087 networkClosed = ourClosed->header().hash;
2088 switchLedgers = false;
2089 }
2090 else
2091 {
2092 networkClosed = closedLedger;
2093 }
2094
2095 if (!switchLedgers)
2096 return false;
2097
2098 auto consensus = ledgerMaster_.getLedgerByHash(closedLedger);
2099
2100 if (!consensus)
2101 {
2102 consensus = registry_.get().getInboundLedgers().acquire(
2103 closedLedger, 0, InboundLedger::Reason::CONSENSUS);
2104 }
2105
2106 if (consensus &&
2107 (!ledgerMaster_.canBeCurrent(consensus) ||
2108 !ledgerMaster_.isCompatible(*consensus, journal_.debug(), "Not switching")))
2109 {
2110 // Don't switch to a ledger not on the validated chain
2111 // or with an invalid close time or sequence
2112 networkClosed = ourClosed->header().hash;
2113 return false;
2114 }
2115
2116 JLOG(journal_.warn()) << "We are not running on the consensus ledger";
2117 JLOG(journal_.info()) << "Our LCL: " << ourClosed->header().hash << getJson({*ourClosed, {}});
2118 JLOG(journal_.info()) << "Net LCL " << closedLedger;
2119
2121 {
2123 }
2124
2125 if (consensus)
2126 {
2127 // FIXME: If this rewinds the ledger sequence, or has the same
2128 // sequence, we should update the status on any stored transactions
2129 // in the invalidated ledgers.
2130 switchLastClosedLedger(consensus);
2131 }
2132
2133 return true;
2134}
2135
2136void
2138{
2139 // set the newLCL as our last closed ledger -- this is abnormal code
2140 JLOG(journal_.error()) << "JUMP last closed ledger to " << newLCL->header().hash;
2141
2143
2144 // Update fee computations.
2145 registry_.get().getTxQ().processClosedLedger(registry_.get().getApp(), *newLCL, true);
2146
2147 // Caller must own master lock
2148 {
2149 // Apply tx in old open ledger to new
2150 // open ledger. Then apply local tx.
2151
2152 auto retries = localTX_->getTxSet();
2153 auto const lastVal = registry_.get().getLedgerMaster().getValidatedLedger();
2155 if (lastVal)
2156 {
2157 rules = makeRulesGivenLedger(*lastVal, registry_.get().getApp().config().features);
2158 }
2159 else
2160 {
2161 rules.emplace(registry_.get().getApp().config().features);
2162 }
2163 registry_.get().getOpenLedger().accept(
2164 registry_.get().getApp(),
2165 *rules,
2166 newLCL,
2167 OrderedTxs({}),
2168 false,
2169 retries,
2170 TapNone,
2171 "jump",
2172 [&](OpenView& view, beast::Journal j) {
2173 // Stuff the ledger with transactions from the queue.
2174 return registry_.get().getTxQ().accept(registry_.get().getApp(), view);
2175 });
2176 }
2177
2178 ledgerMaster_.switchLCL(newLCL);
2179
2180 protocol::TMStatusChange s;
2181 s.set_newevent(protocol::neSWITCHED_LEDGER);
2182 s.set_ledgerseq(newLCL->header().seq);
2183 s.set_networktime(registry_.get().getTimeKeeper().now().time_since_epoch().count());
2184 s.set_ledgerhashprevious(
2185 newLCL->header().parentHash.begin(), newLCL->header().parentHash.size());
2186 s.set_ledgerhash(newLCL->header().hash.begin(), newLCL->header().hash.size());
2187 registry_.get().getOverlay().foreach(
2188 SendAlways(std::make_shared<Message>(s, protocol::mtSTATUS_CHANGE)));
2189}
2190
2191bool
2193 uint256 const& networkClosed,
2195{
2196 XRPL_ASSERT(networkClosed.isNonZero(), "xrpl::NetworkOPsImp::beginConsensus : nonzero input");
2197
2198 auto closingInfo = ledgerMaster_.getCurrentLedger()->header();
2199
2200 JLOG(journal_.info()) << "Consensus time for #" << closingInfo.seq << " with LCL "
2201 << closingInfo.parentHash;
2202
2203 auto prevLedger = ledgerMaster_.getLedgerByHash(closingInfo.parentHash);
2204
2205 if (!prevLedger)
2206 {
2207 // this shouldn't happen unless we jump ledgers
2209 {
2210 JLOG(journal_.warn()) << "Don't have LCL, going to tracking";
2212 CLOG(clog) << "beginConsensus Don't have LCL, going to tracking. ";
2213 }
2214
2215 CLOG(clog) << "beginConsensus no previous ledger. ";
2216 return false;
2217 }
2218
2219 XRPL_ASSERT(
2220 prevLedger->header().hash == closingInfo.parentHash,
2221 "xrpl::NetworkOPsImp::beginConsensus : prevLedger hash matches "
2222 "parent");
2223 XRPL_ASSERT(
2224 closingInfo.parentHash == ledgerMaster_.getClosedLedger()->header().hash,
2225 "xrpl::NetworkOPsImp::beginConsensus : closedLedger parent matches "
2226 "hash");
2227
2228 registry_.get().getValidators().setNegativeUNL(prevLedger->negativeUNL());
2229 TrustChanges const changes = registry_.get().getValidators().updateTrusted(
2230 registry_.get().getValidations().getCurrentNodeIDs(),
2231 closingInfo.parentCloseTime,
2232 *this,
2233 registry_.get().getOverlay(),
2234 registry_.get().getHashRouter());
2235
2236 if (!changes.added.empty() || !changes.removed.empty())
2237 {
2238 registry_.get().getValidations().trustChanged(changes.added, changes.removed);
2239 // Update the AmendmentTable so it tracks the current validators.
2240 registry_.get().getAmendmentTable().trustChanged(
2241 registry_.get().getValidators().getQuorumKeys().second);
2242 }
2243
2244 consensus_.startRound(
2245 registry_.get().getTimeKeeper().closeTime(),
2246 networkClosed,
2247 prevLedger,
2248 changes.removed,
2249 changes.added,
2250 clog);
2251
2252 ConsensusPhase const currPhase = consensus_.phase();
2253 if (lastConsensusPhase_ != currPhase)
2254 {
2255 reportConsensusStateChange(currPhase);
2256 lastConsensusPhase_ = currPhase;
2257 }
2258
2259 JLOG(journal_.debug()) << "Initiating consensus engine";
2260 return true;
2261}
2262
2263bool
2265{
2266 auto const& peerKey = peerPos.publicKey();
2267 if (validatorPK_ == peerKey || validatorMasterPK_ == peerKey)
2268 {
2269 // Could indicate a operator misconfiguration where two nodes are
2270 // running with the same validator key configured, so this isn't fatal,
2271 // and it doesn't necessarily indicate peer misbehavior. But since this
2272 // is a trusted message, it could be a very big deal. Either way, we
2273 // don't want to relay the proposal. Note that the byzantine behavior
2274 // detection in handleNewValidation will notify other peers.
2275 //
2276 // Another, innocuous explanation is unusual message routing and delays,
2277 // causing this node to receive its own messages back.
2278 JLOG(journal_.error()) << "Received a proposal signed by MY KEY from a peer. This may "
2279 "indicate a misconfiguration where another node has the same "
2280 "validator key, or may be caused by unusual message routing and "
2281 "delays.";
2282 return false;
2283 }
2284
2285 return consensus_.peerProposal(registry_.get().getTimeKeeper().closeTime(), peerPos);
2286}
2287
2288void
2290{
2291 // We now have an additional transaction set
2292 // Inform peers we have this set
2293 protocol::TMHaveTransactionSet msg;
2294 msg.set_hash(map->getHash().asUInt256().begin(), 256 / 8);
2295 msg.set_status(protocol::tsHAVE);
2296 registry_.get().getOverlay().foreach(
2297 SendAlways(std::make_shared<Message>(msg, protocol::mtHAVE_SET)));
2298
2299 // We acquired it because consensus asked us to
2300 if (fromAcquire)
2301 consensus_.gotTxSet(registry_.get().getTimeKeeper().closeTime(), RCLTxSet{map});
2302}
2303
2304void
2306{
2307 uint256 const deadLedger = ledgerMaster_.getClosedLedger()->header().parentHash;
2308 for (auto const& it : registry_.get().getOverlay().getActivePeers())
2309 {
2310 if (it && (it->getClosedLedgerHash() == deadLedger))
2311 {
2312 JLOG(journal_.trace()) << "Killing obsolete peer status";
2313 it->cycleStatus();
2314 }
2315 }
2316
2317 uint256 networkClosed;
2318 bool const ledgerChange =
2319 checkLastClosedLedger(registry_.get().getOverlay().getActivePeers(), networkClosed);
2320
2321 if (networkClosed.isZero())
2322 {
2323 CLOG(clog) << "endConsensus last closed ledger is zero. ";
2324 return;
2325 }
2326
2327 // WRITEME: Unless we are in FULL and in the process of doing a consensus,
2328 // we must count how many nodes share our LCL, how many nodes disagree with
2329 // our LCL, and how many validations our LCL has. We also want to check
2330 // timing to make sure there shouldn't be a newer LCL. We need this
2331 // information to do the next three tests.
2332
2333 if (((mode_ == OperatingMode::CONNECTED) || (mode_ == OperatingMode::SYNCING)) && !ledgerChange)
2334 {
2335 // Count number of peers that agree with us and UNL nodes whose
2336 // validations we have for LCL. If the ledger is good enough, go to
2337 // TRACKING - TODO
2338 if (!needNetworkLedger_)
2340 }
2341
2343 !ledgerChange)
2344 {
2345 // check if the ledger is good enough to go to FULL
2346 // Note: Do not go to FULL if we don't have the previous ledger
2347 // check if the ledger is bad enough to go to CONNECTED -- TODO
2348 auto current = ledgerMaster_.getCurrentLedger();
2349 if (registry_.get().getTimeKeeper().now() <
2350 (current->header().parentCloseTime + 2 * current->header().closeTimeResolution))
2351 {
2353 }
2354 }
2355
2356 beginConsensus(networkClosed, clog);
2357}
2358
2359void
2367
2368void
2370{
2371 // Hold each locked subscriber alive until after streamLock_ is released:
2372 // if this is the last reference, ~InfoSub re-acquires streamLock_ (via its
2373 // unsub* calls), which would self-deadlock on this non-recursive mutex.
2374 // Declared before the lock so it is destroyed after the lock is dropped.
2376
2377 // VFALCO consider std::shared_mutex
2378 std::scoped_lock const sl(streamLock_);
2379
2380 if (!streamMaps_[SManifests].empty())
2381 {
2383
2384 jvObj[jss::type] = "manifestReceived";
2385 jvObj[jss::master_key] = toBase58(TokenType::NodePublic, mo.masterKey);
2386 if (mo.signingKey)
2387 jvObj[jss::signing_key] = toBase58(TokenType::NodePublic, *mo.signingKey);
2388 jvObj[jss::seq] = json::UInt(mo.sequence);
2389 if (auto sig = mo.getSignature())
2390 jvObj[jss::signature] = strHex(*sig);
2391 jvObj[jss::master_signature] = strHex(mo.getMasterSignature());
2392 if (!mo.domain.empty())
2393 jvObj[jss::domain] = mo.domain;
2394 jvObj[jss::manifest] = strHex(mo.serialized);
2395
2396 for (auto i = streamMaps_[SManifests].begin(); i != streamMaps_[SManifests].end();)
2397 {
2398 if (auto p = i->second.lock())
2399 {
2400 p->send(jvObj, true);
2401 toRelease.push_back(std::move(p));
2402 ++i;
2403 }
2404 else
2405 {
2406 i = streamMaps_[SManifests].erase(i);
2407 }
2408 }
2409 }
2410}
2411
2413 XRPAmount fee,
2414 TxQ::Metrics escalationMetrics, // trivially copyable
2415 LoadFeeTrack const& loadFeeTrack)
2416 : loadFactorServer{loadFeeTrack.getLoadFactor()}
2417 , loadBaseServer{loadFeeTrack.getLoadBase()}
2418 , baseFee{fee}
2419 , em{escalationMetrics}
2420{
2421}
2422
2423bool
2425{
2427 baseFee != b.baseFee || em.has_value() != b.em.has_value())
2428 return true;
2429
2430 if (em && b.em)
2431 {
2432 return (
2433 em->minProcessingFeeLevel != b.em->minProcessingFeeLevel ||
2434 em->openLedgerFeeLevel != b.em->openLedgerFeeLevel ||
2435 em->referenceFeeLevel != b.em->referenceFeeLevel);
2436 }
2437
2438 return false;
2439}
2440
2441// Need to cap to uint64 to uint32 due to JSON limitations
2442static std::uint32_t
2444{
2446
2447 return std::min(kMax32, v);
2448};
2449
2450void
2452{
2453 // Hold each locked subscriber alive until after streamLock_ is released; a
2454 // last-reference ~InfoSub would otherwise re-acquire this non-recursive
2455 // mutex and self-deadlock. Declared before the lock, destroyed after it.
2457
2458 // VFALCO TODO Don't hold the lock across calls to send...make a copy of the
2459 // list into a local array while holding the lock then release
2460 // the lock and call send on everyone.
2461 //
2462 std::scoped_lock const sl(streamLock_);
2463
2464 if (!streamMaps_[SServer].empty())
2465 {
2467
2469 registry_.get().getOpenLedger().current()->fees().base,
2470 registry_.get().getTxQ().getMetrics(*registry_.get().getOpenLedger().current()),
2471 registry_.get().getFeeTrack()};
2472
2473 jvObj[jss::type] = "serverStatus";
2474 jvObj[jss::server_status] = strOperatingMode();
2475 jvObj[jss::load_base] = f.loadBaseServer;
2476 jvObj[jss::load_factor_server] = f.loadFactorServer;
2477 jvObj[jss::base_fee] = f.baseFee.jsonClipped();
2478
2479 if (f.em)
2480 {
2481 auto const loadFactor = std::max(
2483 mulDiv(f.em->openLedgerFeeLevel, f.loadBaseServer, f.em->referenceFeeLevel)
2484 .value_or(xrpl::kMuldivMax));
2485
2486 jvObj[jss::load_factor] = trunc32(loadFactor);
2487 jvObj[jss::load_factor_fee_escalation] = f.em->openLedgerFeeLevel.jsonClipped();
2488 jvObj[jss::load_factor_fee_queue] = f.em->minProcessingFeeLevel.jsonClipped();
2489 jvObj[jss::load_factor_fee_reference] = f.em->referenceFeeLevel.jsonClipped();
2490 }
2491 else
2492 {
2493 jvObj[jss::load_factor] = f.loadFactorServer;
2494 }
2495
2496 lastFeeSummary_ = f;
2497
2498 for (auto i = streamMaps_[SServer].begin(); i != streamMaps_[SServer].end();)
2499 {
2500 InfoSub::pointer p = i->second.lock();
2501
2502 // VFALCO TODO research the possibility of using thread queues and
2503 // linearizing the deletion of subscribers with the
2504 // sending of JSON data.
2505 if (p)
2506 {
2507 p->send(jvObj, true);
2508 toRelease.push_back(std::move(p));
2509 ++i;
2510 }
2511 else
2512 {
2513 i = streamMaps_[SServer].erase(i);
2514 }
2515 }
2516 }
2517}
2518
2519void
2521{
2522 // Hold each locked subscriber alive until after streamLock_ is released; a
2523 // last-reference ~InfoSub would otherwise re-acquire this non-recursive
2524 // mutex and self-deadlock. Declared before the lock, destroyed after it.
2526
2527 std::scoped_lock const sl(streamLock_);
2528
2529 auto& streamMap = streamMaps_[SConsensusPhase];
2530 if (!streamMap.empty())
2531 {
2533 jvObj[jss::type] = "consensusPhase";
2534 jvObj[jss::consensus] = to_string(phase);
2535
2536 for (auto i = streamMap.begin(); i != streamMap.end();)
2537 {
2538 if (auto p = i->second.lock())
2539 {
2540 p->send(jvObj, true);
2541 toRelease.push_back(std::move(p));
2542 ++i;
2543 }
2544 else
2545 {
2546 i = streamMap.erase(i);
2547 }
2548 }
2549 }
2550}
2551
2552void
2554{
2555 // Hold each locked subscriber alive until after streamLock_ is released; a
2556 // last-reference ~InfoSub would otherwise re-acquire this non-recursive
2557 // mutex and self-deadlock. Declared before the lock, destroyed after it.
2559
2560 // VFALCO consider std::shared_mutex
2561 std::scoped_lock const sl(streamLock_);
2562
2563 if (!streamMaps_[SValidations].empty())
2564 {
2566
2567 auto const signerPublic = val->getSignerPublic();
2568
2569 jvObj[jss::type] = "validationReceived";
2570 jvObj[jss::validation_public_key] = toBase58(TokenType::NodePublic, signerPublic);
2571 jvObj[jss::ledger_hash] = to_string(val->getLedgerHash());
2572 jvObj[jss::signature] = strHex(val->getSignature());
2573 jvObj[jss::full] = val->isFull();
2574 jvObj[jss::flags] = val->getFlags();
2575 jvObj[jss::signing_time] = *(*val)[~sfSigningTime];
2576 jvObj[jss::data] = strHex(val->getSerializer().slice());
2577 jvObj[jss::network_id] = registry_.get().getNetworkIDService().getNetworkID();
2578
2579 if (auto version = (*val)[~sfServerVersion])
2580 jvObj[jss::server_version] = std::to_string(*version);
2581
2582 if (auto cookie = (*val)[~sfCookie])
2583 jvObj[jss::cookie] = std::to_string(*cookie);
2584
2585 if (auto hash = (*val)[~sfValidatedHash])
2586 jvObj[jss::validated_hash] = strHex(*hash);
2587
2588 auto const masterKey = registry_.get().getValidatorManifests().getMasterKey(signerPublic);
2589
2590 if (masterKey != signerPublic)
2591 jvObj[jss::master_key] = toBase58(TokenType::NodePublic, masterKey);
2592
2593 // NOTE *seq is a number, but old API versions used string. We replace
2594 // number with a string using MultiApiJson near end of this function
2595 if (auto const seq = (*val)[~sfLedgerSequence])
2596 jvObj[jss::ledger_index] = *seq;
2597
2598 if (val->isFieldPresent(sfAmendments))
2599 {
2600 jvObj[jss::amendments] = json::Value(json::ValueType::Array);
2601 for (auto const& amendment : val->getFieldV256(sfAmendments))
2602 jvObj[jss::amendments].append(to_string(amendment));
2603 }
2604
2605 if (auto const closeTime = (*val)[~sfCloseTime])
2606 jvObj[jss::close_time] = *closeTime;
2607
2608 if (auto const loadFee = (*val)[~sfLoadFee])
2609 jvObj[jss::load_fee] = *loadFee;
2610
2611 if (auto const baseFee = val->at(~sfBaseFee))
2612 jvObj[jss::base_fee] = static_cast<double>(*baseFee);
2613
2614 if (auto const reserveBase = val->at(~sfReserveBase))
2615 jvObj[jss::reserve_base] = *reserveBase;
2616
2617 if (auto const reserveInc = val->at(~sfReserveIncrement))
2618 jvObj[jss::reserve_inc] = *reserveInc;
2619
2620 // (The ~ operator converts the Proxy to a std::optional, which
2621 // simplifies later operations)
2622 if (auto const baseFeeXRP = ~val->at(~sfBaseFeeDrops); baseFeeXRP && baseFeeXRP->native())
2623 jvObj[jss::base_fee] = baseFeeXRP->xrp().jsonClipped();
2624
2625 if (auto const reserveBaseXRP = ~val->at(~sfReserveBaseDrops);
2626 reserveBaseXRP && reserveBaseXRP->native())
2627 jvObj[jss::reserve_base] = reserveBaseXRP->xrp().jsonClipped();
2628
2629 if (auto const reserveIncXRP = ~val->at(~sfReserveIncrementDrops);
2630 reserveIncXRP && reserveIncXRP->native())
2631 jvObj[jss::reserve_inc] = reserveIncXRP->xrp().jsonClipped();
2632
2633 // NOTE Use MultiApiJson to publish two slightly different JSON objects
2634 // for consumers supporting different API versions
2635 MultiApiJson multiObj{jvObj};
2636 multiObj.visit(
2638 [](json::Value& jvTx) {
2639 // Type conversion for older API versions to string
2640 if (jvTx.isMember(jss::ledger_index))
2641 {
2642 jvTx[jss::ledger_index] = std::to_string(jvTx[jss::ledger_index].asUInt());
2643 }
2644 });
2645
2646 for (auto i = streamMaps_[SValidations].begin(); i != streamMaps_[SValidations].end();)
2647 {
2648 if (auto p = i->second.lock())
2649 {
2650 multiObj.visit(
2651 p->getApiVersion(), //
2652 [&](json::Value const& jv) { p->send(jv, true); });
2653 toRelease.push_back(std::move(p));
2654 ++i;
2655 }
2656 else
2657 {
2658 i = streamMaps_[SValidations].erase(i);
2659 }
2660 }
2661 }
2662}
2663
2664void
2666{
2667 // Hold each locked subscriber alive until after streamLock_ is released; a
2668 // last-reference ~InfoSub would otherwise re-acquire this non-recursive
2669 // mutex and self-deadlock. Declared before the lock, destroyed after it.
2671
2672 std::scoped_lock const sl(streamLock_);
2673
2674 if (!streamMaps_[SPeerStatus].empty())
2675 {
2676 json::Value jvObj(func());
2677
2678 jvObj[jss::type] = "peerStatusChange";
2679
2680 for (auto i = streamMaps_[SPeerStatus].begin(); i != streamMaps_[SPeerStatus].end();)
2681 {
2682 InfoSub::pointer p = i->second.lock();
2683
2684 if (p)
2685 {
2686 p->send(jvObj, true);
2687 toRelease.push_back(std::move(p));
2688 ++i;
2689 }
2690 else
2691 {
2692 i = streamMaps_[SPeerStatus].erase(i);
2693 }
2694 }
2695 }
2696}
2697
2698void
2700{
2701 using namespace std::chrono_literals;
2702 if (om == OperatingMode::CONNECTED)
2703 {
2704 if (registry_.get().getLedgerMaster().getValidatedLedgerAge() < 1min)
2706 }
2707 else if (om == OperatingMode::SYNCING)
2708 {
2709 if (registry_.get().getLedgerMaster().getValidatedLedgerAge() >= 1min)
2711 }
2712
2713 if ((om > OperatingMode::CONNECTED) && isBlocked())
2715
2716 if (mode_ == om)
2717 return;
2718
2719 mode_ = om;
2720
2721 accounting_.mode(om);
2722
2723 JLOG(journal_.info()) << "STATE->" << strOperatingMode();
2724 pubServer();
2725}
2726
2727bool
2729{
2730 JLOG(journal_.trace()) << "recvValidation " << val->getLedgerHash() << " from " << source;
2731
2733 BypassAccept bypassAccept = BypassAccept::No;
2734 try
2735 {
2736 if (pendingValidations_.contains(val->getLedgerHash()))
2737 {
2738 bypassAccept = BypassAccept::Yes;
2739 }
2740 else
2741 {
2742 pendingValidations_.insert(val->getLedgerHash());
2743 }
2744 ScopeUnlock const unlock(lock);
2745 handleNewValidation(registry_.get().getApp(), val, source, bypassAccept, journal_);
2746 }
2747 catch (std::exception const& e)
2748 {
2749 JLOG(journal_.warn()) << "Exception thrown for handling new validation "
2750 << val->getLedgerHash() << ": " << e.what();
2751 }
2752 catch (...)
2753 {
2754 JLOG(journal_.warn()) << "Unknown exception thrown for handling new validation "
2755 << val->getLedgerHash();
2756 }
2757 if (bypassAccept == BypassAccept::No)
2758 {
2759 pendingValidations_.erase(val->getLedgerHash());
2760 }
2761 lock.unlock();
2762
2763 pubValidation(val);
2764
2765 JLOG(journal_.debug()) << [this, &val]() -> auto {
2767 ss << "VALIDATION: " << val->render() << " master_key: ";
2768 auto master = registry_.get().getValidators().getTrustedKey(val->getSignerPublic());
2769 if (master)
2770 {
2771 ss << toBase58(TokenType::NodePublic, *master);
2772 }
2773 else
2774 {
2775 ss << "none";
2776 }
2777 return ss.str();
2778 }();
2779
2780 // We will always relay trusted validations; if configured, we will
2781 // also relay all untrusted validations.
2782 return registry_.get().getApp().config().relayUntrustedValidations == 1 || val->isTrusted();
2783}
2784
2787{
2788 return consensus_.getJson(true);
2789}
2790
2792NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters)
2793{
2795
2796 // System-level warnings
2797 {
2799 if (isAmendmentBlocked())
2800 {
2802 w[jss::id] = WarnRpcAmendmentBlocked;
2803 w[jss::message] =
2804 "This server is amendment blocked, and must be updated to be "
2805 "able to stay in sync with the network.";
2806 }
2807 if (isUNLBlocked())
2808 {
2810 w[jss::id] = WarnRpcExpiredValidatorList;
2811 w[jss::message] =
2812 "This server has an expired validator list. validators.txt "
2813 "may be incorrectly configured or some [validator_list_sites] "
2814 "may be unreachable.";
2815 }
2816 if (admin && isAmendmentWarned())
2817 {
2819 w[jss::id] = WarnRpcUnsupportedMajority;
2820 w[jss::message] =
2821 "One or more unsupported amendments have reached majority. "
2822 "Upgrade to the latest version before they are activated "
2823 "to avoid being amendment blocked.";
2824 if (auto const expected =
2825 registry_.get().getAmendmentTable().firstUnsupportedExpected())
2826 {
2827 auto& d = w[jss::details] = json::ValueType::Object;
2828 d[jss::expected_date] = expected->time_since_epoch().count();
2829 d[jss::expected_date_UTC] = to_string(*expected);
2830 }
2831 }
2832
2833 if (warnings.size() != 0u)
2834 info[jss::warnings] = std::move(warnings);
2835 }
2836
2837 // hostid: unique string describing the machine
2838 if (human)
2839 info[jss::hostid] = getHostId(admin);
2840
2841 // domain: if configured with a domain, report it:
2842 if (!registry_.get().getApp().config().serverDomain.empty())
2843 info[jss::server_domain] = registry_.get().getApp().config().serverDomain;
2844
2845 info[jss::build_version] = build_info::getVersionString();
2846
2847 info[jss::server_state] = strOperatingMode(admin);
2848
2849 info[jss::time] =
2850 to_string(std::chrono::floor<std::chrono::microseconds>(std::chrono::system_clock::now()));
2851
2853 info[jss::network_ledger] = "waiting";
2854
2855 info[jss::validation_quorum] =
2856 static_cast<json::UInt>(registry_.get().getValidators().quorum());
2857
2858 if (admin)
2859 {
2860 // Note: By default the node size is "tiny". When parsing it's an error if the final
2861 // NODE_SIZE is over 4 so below code should be safe.
2862 // NOLINTNEXTLINE(bugprone-switch-missing-default-case)
2863 switch (registry_.get().getApp().config().nodeSize)
2864 {
2865 case 0:
2866 info[jss::node_size] = "tiny";
2867 break;
2868 case 1:
2869 info[jss::node_size] = "small";
2870 break;
2871 case 2:
2872 info[jss::node_size] = "medium";
2873 break;
2874 case 3:
2875 info[jss::node_size] = "large";
2876 break;
2877 case 4:
2878 info[jss::node_size] = "huge";
2879 break;
2880 }
2881
2882 auto when = registry_.get().getValidators().expires();
2883
2884 if (!human)
2885 {
2886 if (when)
2887 {
2888 info[jss::validator_list_expires] =
2889 safeCast<json::UInt>(when->time_since_epoch().count());
2890 }
2891 else
2892 {
2893 info[jss::validator_list_expires] = 0;
2894 }
2895 }
2896 else
2897 {
2898 auto& x = (info[jss::validator_list] = json::ValueType::Object);
2899
2900 x[jss::count] = static_cast<json::UInt>(registry_.get().getValidators().count());
2901
2902 if (when)
2903 {
2904 if (*when == TimeKeeper::time_point::max())
2905 {
2906 x[jss::expiration] = "never";
2907 x[jss::status] = "active";
2908 }
2909 else
2910 {
2911 x[jss::expiration] = to_string(*when);
2912
2913 if (*when > registry_.get().getTimeKeeper().now())
2914 {
2915 x[jss::status] = "active";
2916 }
2917 else
2918 {
2919 x[jss::status] = "expired";
2920 }
2921 }
2922 }
2923 else
2924 {
2925 x[jss::status] = "unknown";
2926 x[jss::expiration] = "unknown";
2927 }
2928 }
2929
2930 if (!xrpl::git::getCommitHash().empty() || !xrpl::git::getBuildBranch().empty())
2931 {
2932 auto& x = (info[jss::git] = json::ValueType::Object);
2933 if (!xrpl::git::getCommitHash().empty())
2934 x[jss::hash] = xrpl::git::getCommitHash();
2935 if (!xrpl::git::getBuildBranch().empty())
2936 x[jss::branch] = xrpl::git::getBuildBranch();
2937 }
2938 }
2939 info[jss::io_latency_ms] =
2940 static_cast<json::UInt>(registry_.get().getApp().getIOLatency().count());
2941
2942 if (admin)
2943 {
2944 if (auto const localPubKey = registry_.get().getValidators().localPublicKey();
2945 localPubKey && registry_.get().getApp().getValidationPublicKey())
2946 {
2947 info[jss::pubkey_validator] = toBase58(TokenType::NodePublic, localPubKey.value());
2948 }
2949 else
2950 {
2951 info[jss::pubkey_validator] = "none";
2952 }
2953 }
2954
2955 if (counters)
2956 {
2957 info[jss::counters] = registry_.get().getPerfLog().countersJson();
2958
2960 registry_.get().getNodeStore().getCountsJson(nodestore);
2961 info[jss::counters][jss::nodestore] = nodestore;
2962 info[jss::current_activities] = registry_.get().getPerfLog().currentJson();
2963 }
2964
2965 info[jss::pubkey_node] =
2966 toBase58(TokenType::NodePublic, registry_.get().getApp().nodeIdentity().first);
2967
2968 info[jss::complete_ledgers] = registry_.get().getLedgerMaster().getCompleteLedgers();
2969
2971 info[jss::amendment_blocked] = true;
2972
2973 auto const fp = ledgerMaster_.getFetchPackCacheSize();
2974
2975 if (fp != 0)
2976 info[jss::fetch_pack] = json::UInt(fp);
2977
2978 info[jss::peers] = json::UInt(registry_.get().getOverlay().size());
2979
2981 lastClose[jss::proposers] = json::UInt(consensus_.prevProposers());
2982
2983 if (human)
2984 {
2985 lastClose[jss::converge_time_s] =
2987 }
2988 else
2989 {
2990 lastClose[jss::converge_time] = json::Int(consensus_.prevRoundTime().count());
2991 }
2992
2993 info[jss::last_close] = lastClose;
2994
2995 // info[jss::consensus] = consensus_.getJson();
2996
2997 if (admin)
2998 info[jss::load] = jobQueue_.getJson();
2999
3000 if (auto const netid = registry_.get().getOverlay().networkID())
3001 info[jss::network_id] = static_cast<json::UInt>(*netid);
3002
3003 auto const escalationMetrics =
3004 registry_.get().getTxQ().getMetrics(*registry_.get().getOpenLedger().current());
3005
3006 auto const loadFactorServer = registry_.get().getFeeTrack().getLoadFactor();
3007 auto const loadBaseServer = registry_.get().getFeeTrack().getLoadBase();
3008 /* Scale the escalated fee level to unitless "load factor".
3009 In practice, this just strips the units, but it will continue
3010 to work correctly if either base value ever changes. */
3011 auto const loadFactorFeeEscalation = mulDiv(
3012 escalationMetrics.openLedgerFeeLevel,
3013 loadBaseServer,
3014 escalationMetrics.referenceFeeLevel)
3016
3017 auto const loadFactor =
3018 std::max(safeCast<std::uint64_t>(loadFactorServer), loadFactorFeeEscalation);
3019
3020 if (!human)
3021 {
3022 info[jss::load_base] = loadBaseServer;
3023 info[jss::load_factor] = trunc32(loadFactor);
3024 info[jss::load_factor_server] = loadFactorServer;
3025
3026 /* json::Value doesn't support uint64, so clamp to max
3027 uint32 value. This is mostly theoretical, since there
3028 probably isn't enough extant XRP to drive the factor
3029 that high.
3030 */
3031 info[jss::load_factor_fee_escalation] = escalationMetrics.openLedgerFeeLevel.jsonClipped();
3032 info[jss::load_factor_fee_queue] = escalationMetrics.minProcessingFeeLevel.jsonClipped();
3033 info[jss::load_factor_fee_reference] = escalationMetrics.referenceFeeLevel.jsonClipped();
3034 }
3035 else
3036 {
3037 info[jss::load_factor] = static_cast<double>(loadFactor) / loadBaseServer;
3038
3039 if (loadFactorServer != loadFactor)
3040 info[jss::load_factor_server] = static_cast<double>(loadFactorServer) / loadBaseServer;
3041
3042 if (admin)
3043 {
3044 std::uint32_t fee = registry_.get().getFeeTrack().getLocalFee();
3045 if (fee != loadBaseServer)
3046 info[jss::load_factor_local] = static_cast<double>(fee) / loadBaseServer;
3047 fee = registry_.get().getFeeTrack().getRemoteFee();
3048 if (fee != loadBaseServer)
3049 info[jss::load_factor_net] = static_cast<double>(fee) / loadBaseServer;
3050 fee = registry_.get().getFeeTrack().getClusterFee();
3051 if (fee != loadBaseServer)
3052 info[jss::load_factor_cluster] = static_cast<double>(fee) / loadBaseServer;
3053 }
3054 if (escalationMetrics.openLedgerFeeLevel != escalationMetrics.referenceFeeLevel &&
3055 (admin || loadFactorFeeEscalation != loadFactor))
3056 {
3057 info[jss::load_factor_fee_escalation] =
3058 escalationMetrics.openLedgerFeeLevel.decimalFromReference(
3059 escalationMetrics.referenceFeeLevel);
3060 }
3061 if (escalationMetrics.minProcessingFeeLevel != escalationMetrics.referenceFeeLevel)
3062 {
3063 info[jss::load_factor_fee_queue] =
3064 escalationMetrics.minProcessingFeeLevel.decimalFromReference(
3065 escalationMetrics.referenceFeeLevel);
3066 }
3067 }
3068
3069 bool valid = false;
3070 auto lpClosed = ledgerMaster_.getValidatedLedger();
3071
3072 if (lpClosed)
3073 {
3074 valid = true;
3075 }
3076 else
3077 {
3078 lpClosed = ledgerMaster_.getClosedLedger();
3079 }
3080
3081 if (lpClosed)
3082 {
3083 XRPAmount const baseFee = lpClosed->fees().base;
3085 l[jss::seq] = json::UInt(lpClosed->header().seq);
3086 l[jss::hash] = to_string(lpClosed->header().hash);
3087
3088 if (!human)
3089 {
3090 l[jss::base_fee] = baseFee.jsonClipped();
3091 l[jss::reserve_base] = lpClosed->fees().reserve.jsonClipped();
3092 l[jss::reserve_inc] = lpClosed->fees().increment.jsonClipped();
3093 l[jss::close_time] =
3094 json::Value::UInt(lpClosed->header().closeTime.time_since_epoch().count());
3095 }
3096 else
3097 {
3098 l[jss::base_fee_xrp] = baseFee.decimalXRP();
3099 l[jss::reserve_base_xrp] = lpClosed->fees().reserve.decimalXRP();
3100 l[jss::reserve_inc_xrp] = lpClosed->fees().increment.decimalXRP();
3101
3102 if (auto const closeOffset = registry_.get().getTimeKeeper().closeOffset();
3103 std::abs(closeOffset.count()) >= 60)
3104 l[jss::close_time_offset] = static_cast<std::uint32_t>(closeOffset.count());
3105
3106 static constexpr std::chrono::seconds kHighAgeThreshold{1000000};
3107 if (ledgerMaster_.haveValidated())
3108 {
3109 auto const age = ledgerMaster_.getValidatedLedgerAge();
3110 l[jss::age] = json::UInt(age < kHighAgeThreshold ? age.count() : 0);
3111 }
3112 else
3113 {
3114 auto lCloseTime = lpClosed->header().closeTime;
3115 auto closeTime = registry_.get().getTimeKeeper().closeTime();
3116 if (lCloseTime <= closeTime)
3117 {
3118 using namespace std::chrono_literals;
3119 auto age = closeTime - lCloseTime;
3120 l[jss::age] = json::UInt(age < kHighAgeThreshold ? age.count() : 0);
3121 }
3122 }
3123 }
3124
3125 if (valid)
3126 {
3127 info[jss::validated_ledger] = l;
3128 }
3129 else
3130 {
3131 info[jss::closed_ledger] = l;
3132 }
3133
3134 auto lpPublished = ledgerMaster_.getPublishedLedger();
3135 if (!lpPublished)
3136 {
3137 info[jss::published_ledger] = "none";
3138 }
3139 else if (lpPublished->header().seq != lpClosed->header().seq)
3140 {
3141 info[jss::published_ledger] = lpPublished->header().seq;
3142 }
3143 }
3144
3145 accounting_.json(info);
3146 info[jss::uptime] = UptimeClock::now().time_since_epoch().count();
3147 info[jss::jq_trans_overflow] =
3148 std::to_string(registry_.get().getOverlay().getJqTransOverflow());
3149 info[jss::peer_disconnects] = std::to_string(registry_.get().getOverlay().getPeerDisconnect());
3150 info[jss::peer_disconnects_resources] =
3151 std::to_string(registry_.get().getOverlay().getPeerDisconnectCharges());
3152
3153 // This array must be sorted in increasing order.
3154 static constexpr std::array<std::string_view, 7> kProtocols{
3155 "http", "https", "peer", "ws", "ws2", "wss", "wss2"};
3156 static_assert(std::ranges::is_sorted(kProtocols));
3157 {
3159 for (auto const& port : registry_.get().getServerHandler().setup().ports)
3160 {
3161 // Don't publish admin ports for non-admin users
3162 if (!admin &&
3163 !(port.adminNetsV4.empty() && port.adminNetsV6.empty() && port.adminUser.empty() &&
3164 port.adminPassword.empty()))
3165 continue;
3167 // NOLINTNEXTLINE(modernize-use-ranges)
3169 std::begin(port.protocol),
3170 std::end(port.protocol),
3171 std::begin(kProtocols),
3172 std::end(kProtocols),
3173 std::back_inserter(proto));
3174 if (!proto.empty())
3175 {
3176 auto& jv = ports.append(json::Value(json::ValueType::Object));
3177 jv[jss::port] = std::to_string(port.port);
3178 jv[jss::protocol] = json::Value{json::ValueType::Array};
3179 for (auto const& p : proto)
3180 jv[jss::protocol].append(p);
3181 }
3182 }
3183
3184 if (registry_.get().getApp().config().exists(Sections::kPortGrpc))
3185 {
3186 auto const& grpcSection =
3187 registry_.get().getApp().config().section(Sections::kPortGrpc);
3188 auto const optPort = grpcSection.get(Keys::kPort);
3189 if (optPort && grpcSection.get(Keys::kIp))
3190 {
3191 auto& jv = ports.append(json::Value(json::ValueType::Object));
3192 jv[jss::port] = *optPort;
3193 jv[jss::protocol] = json::Value{json::ValueType::Array};
3194 jv[jss::protocol].append("grpc");
3195 }
3196 }
3197 info[jss::ports] = std::move(ports);
3198 }
3199
3200 return info;
3201}
3202
3203void
3205{
3206 registry_.get().getInboundLedgers().clearFailures();
3207}
3208
3211{
3212 return registry_.get().getInboundLedgers().getInfo();
3213}
3214
3215void
3217 std::shared_ptr<ReadView const> const& ledger,
3218 std::shared_ptr<STTx const> const& transaction,
3219 TER result)
3220{
3221 // never publish an inner txn inside a batch txn. The flag should
3222 // only be set if the Batch feature is enabled. If Batch is not
3223 // enabled, the flag is always invalid, so don't publish it
3224 // regardless.
3225 if (transaction->isFlag(tfInnerBatchTxn))
3226 return;
3227
3228 MultiApiJson const jvObj = transJson(transaction, result, false, ledger, std::nullopt);
3229
3230 {
3231 // Hold each locked subscriber alive until after streamLock_ is
3232 // released; a last-reference ~InfoSub would otherwise re-acquire this
3233 // non-recursive mutex and self-deadlock. Declared before the lock,
3234 // destroyed after the block ends.
3236
3237 std::scoped_lock const sl(streamLock_);
3238
3239 auto it = streamMaps_[SRtTransactions].begin();
3240 while (it != streamMaps_[SRtTransactions].end())
3241 {
3242 InfoSub::pointer p = it->second.lock();
3243
3244 if (p)
3245 {
3246 jvObj.visit(
3247 p->getApiVersion(), //
3248 [&](json::Value const& jv) { p->send(jv, true); });
3249 toRelease.push_back(std::move(p));
3250 ++it;
3251 }
3252 else
3253 {
3254 it = streamMaps_[SRtTransactions].erase(it);
3255 }
3256 }
3257 }
3258
3259 pubProposedAccountTransaction(ledger, transaction, result);
3260}
3261
3262void
3264{
3265 // Ledgers are published only when they acquire sufficient validations
3266 // Holes are filled across connection loss or other catastrophe
3267
3269 registry_.get().getAcceptedLedgerCache().fetch(lpAccepted->header().hash);
3270 if (!alpAccepted)
3271 {
3272 alpAccepted = std::make_shared<AcceptedLedger>(lpAccepted);
3273 registry_.get().getAcceptedLedgerCache().canonicalizeReplaceClient(
3274 lpAccepted->header().hash, alpAccepted);
3275 }
3276
3277 XRPL_ASSERT(
3278 alpAccepted->getLedger().get() == lpAccepted.get(),
3279 "xrpl::NetworkOPsImp::pubLedger : accepted input");
3280
3281 JLOG(journal_.debug()) << "Publishing ledger " << lpAccepted->header().seq << " "
3282 << lpAccepted->header().hash;
3283
3284 // Stream updates and the account-history kick-off touch different lock
3285 // domains; each helper takes only its own lock, so the two are never held
3286 // together.
3287 publishLedgerStreams(lpAccepted, alpAccepted);
3288 kickoffAccountHistory(alpAccepted);
3289
3290 // Don't lock since pubAcceptedTransaction is locking.
3291 for (auto const& accTx : *alpAccepted)
3292 {
3293 JLOG(journal_.trace()) << "pubAccepted: " << accTx->getJson();
3294 bool const last = &*accTx == &alpAccepted->back();
3295 pubValidatedTransaction(lpAccepted, *accTx, last);
3296 }
3297}
3298
3299void
3301 std::shared_ptr<ReadView const> const& lpAccepted,
3302 std::shared_ptr<AcceptedLedger const> const& alpAccepted)
3303{
3304 // Hold each locked subscriber alive until after streamLock_ is released; a
3305 // last-reference ~InfoSub would otherwise re-acquire this non-recursive
3306 // mutex and self-deadlock. Declared before the lock, destroyed after it;
3307 // covers both the ledger and book-changes loops below.
3309
3310 std::scoped_lock const sl(streamLock_);
3311
3312 if (!streamMaps_[SLedger].empty())
3313 {
3315
3316 jvObj[jss::type] = "ledgerClosed";
3317 jvObj[jss::ledger_index] = lpAccepted->header().seq;
3318 jvObj[jss::ledger_hash] = to_string(lpAccepted->header().hash);
3319 jvObj[jss::ledger_time] =
3320 json::Value::UInt(lpAccepted->header().closeTime.time_since_epoch().count());
3321
3322 jvObj[jss::network_id] = registry_.get().getNetworkIDService().getNetworkID();
3323
3324 if (!lpAccepted->rules().enabled(featureXRPFees))
3325 jvObj[jss::fee_ref] = kFeeUnitsDeprecated;
3326 jvObj[jss::fee_base] = lpAccepted->fees().base.jsonClipped();
3327 jvObj[jss::reserve_base] = lpAccepted->fees().reserve.jsonClipped();
3328 jvObj[jss::reserve_inc] = lpAccepted->fees().increment.jsonClipped();
3329
3330 jvObj[jss::txn_count] = json::UInt(alpAccepted->size());
3331
3333 {
3334 jvObj[jss::validated_ledgers] = registry_.get().getLedgerMaster().getCompleteLedgers();
3335 }
3336 auto it = streamMaps_[SLedger].begin();
3337 while (it != streamMaps_[SLedger].end())
3338 {
3339 InfoSub::pointer p = it->second.lock();
3340 if (p)
3341 {
3342 p->send(jvObj, true);
3343 toRelease.push_back(std::move(p));
3344 ++it;
3345 }
3346 else
3347 {
3348 it = streamMaps_[SLedger].erase(it);
3349 }
3350 }
3351 }
3352
3353 if (!streamMaps_[SBookChanges].empty())
3354 {
3355 json::Value const jvObj = xrpl::rpc::computeBookChanges(lpAccepted);
3356
3357 auto it = streamMaps_[SBookChanges].begin();
3358 while (it != streamMaps_[SBookChanges].end())
3359 {
3360 InfoSub::pointer p = it->second.lock();
3361 if (p)
3362 {
3363 p->send(jvObj, true);
3364 toRelease.push_back(std::move(p));
3365 ++it;
3366 }
3367 else
3368 {
3369 it = streamMaps_[SBookChanges].erase(it);
3370 }
3371 }
3372 }
3373}
3374
3375void
3377{
3378 // Runs exactly once, the first time a ledger is published. The atomic
3379 // exchange lets the common post-first-ledger path return without taking
3380 // accountLock_, while still admitting exactly one caller even if ledger
3381 // publishing is ever made concurrent.
3382 static std::atomic<bool> done{false};
3383 if (done.exchange(true))
3384 return;
3385
3386 // It only reads/writes subAccountHistory_, so it takes accountLock_ alone.
3388 for (auto& outer : subAccountHistory_)
3389 {
3390 for (auto& inner : outer.second)
3391 {
3392 auto& subInfo = inner.second;
3393 if (subInfo.index->separationLedgerSeq == 0)
3394 subAccountHistoryStart(alpAccepted->getLedger(), subInfo);
3395 }
3396 }
3397}
3398
3399void
3401{
3402 ServerFeeSummary const f{
3403 registry_.get().getOpenLedger().current()->fees().base,
3404 registry_.get().getTxQ().getMetrics(*registry_.get().getOpenLedger().current()),
3405 registry_.get().getFeeTrack()};
3406
3407 // only schedule the job if something has changed
3408 if (f != lastFeeSummary_)
3409 {
3410 jobQueue_.addJob(JtClientFeeChange, "PubFee", [this]() { pubServer(); });
3411 }
3412}
3413
3414void
3416{
3417 jobQueue_.addJob(JtClientConsensus, "PubCons", [this, phase]() { pubConsensus(phase); });
3418}
3419
3420inline void
3422{
3423 localTX_->sweep(view);
3424}
3425inline std::size_t
3427{
3428 return localTX_->size();
3429}
3430
3433{
3434 std::scoped_lock const sl(bookLock_);
3435 std::size_t total = 0;
3436 for (auto const& [_, subs] : subBook_)
3437 total += subs.size();
3438 return total;
3439}
3440
3441// This routine should only be used to publish accepted or validated
3442// transactions.
3445 std::shared_ptr<STTx const> const& transaction,
3446 TER result,
3447 bool validated,
3448 std::shared_ptr<ReadView const> const& ledger,
3450{
3452 std::string sToken;
3453 std::string sHuman;
3454
3455 transResultInfo(result, sToken, sHuman);
3456
3457 jvObj[jss::type] = "transaction";
3458 // NOTE jvObj is not a finished object for either API version. After
3459 // it's populated, we need to finish it for a specific API version. This is
3460 // done in a loop, near the end of this function.
3461 jvObj[jss::transaction] = transaction->getJson(JsonOptions::Values::DisableApiPriorV2, false);
3462
3463 if (meta)
3464 {
3465 jvObj[jss::meta] = meta->get().getJson(JsonOptions::Values::None);
3466 rpc::insertDeliveredAmount(jvObj[jss::meta], *ledger, transaction, meta->get());
3467 rpc::insertNFTSyntheticInJson(jvObj, transaction, meta->get());
3468 rpc::insertMPTokenIssuanceID(jvObj[jss::meta], transaction, meta->get());
3469 }
3470
3471 // add CTID where the needed data for it exists
3472 if (auto const& lookup = ledger->txRead(transaction->getTransactionID());
3473 lookup.second && lookup.second->isFieldPresent(sfTransactionIndex))
3474 {
3475 uint32_t const txnSeq = lookup.second->getFieldU32(sfTransactionIndex);
3476 uint32_t netID = registry_.get().getNetworkIDService().getNetworkID();
3477 if (transaction->isFieldPresent(sfNetworkID))
3478 netID = transaction->getFieldU32(sfNetworkID);
3479
3480 if (std::optional<std::string> ctid = rpc::encodeCTID(ledger->header().seq, txnSeq, netID);
3481 ctid)
3482 jvObj[jss::ctid] = *ctid;
3483 }
3484 if (!ledger->open())
3485 jvObj[jss::ledger_hash] = to_string(ledger->header().hash);
3486
3487 if (validated)
3488 {
3489 jvObj[jss::ledger_index] = ledger->header().seq;
3490 jvObj[jss::transaction][jss::date] = ledger->header().closeTime.time_since_epoch().count();
3491 jvObj[jss::validated] = true;
3492 jvObj[jss::close_time_iso] = toStringIso(ledger->header().closeTime);
3493
3494 // WRITEME: Put the account next seq here
3495 }
3496 else
3497 {
3498 jvObj[jss::validated] = false;
3499 jvObj[jss::ledger_current_index] = ledger->header().seq;
3500 }
3501
3502 jvObj[jss::status] = validated ? "closed" : "proposed";
3503 jvObj[jss::engine_result] = sToken;
3504 jvObj[jss::engine_result_code] = result;
3505 jvObj[jss::engine_result_message] = sHuman;
3506
3507 if (transaction->getTxnType() == ttOFFER_CREATE)
3508 {
3509 auto const account = transaction->getAccountID(sfAccount);
3510 auto const amount = transaction->getFieldAmount(sfTakerGets);
3511
3512 // If the offer create is not self funded then add the owner balance
3513 if (account != amount.getIssuer())
3514 {
3515 auto const ownerFunds = accountFunds(
3516 *ledger,
3517 account,
3518 amount,
3521 registry_.get().getJournal("View"));
3522 jvObj[jss::transaction][jss::owner_funds] = ownerFunds.getText();
3523 }
3524 }
3525
3526 std::string const hash = to_string(transaction->getTransactionID());
3527 MultiApiJson multiObj{jvObj};
3529 multiObj.visit(), //
3530 [&]<unsigned Version>(json::Value& jvTx, std::integral_constant<unsigned, Version>) {
3531 rpc::insertDeliverMax(jvTx[jss::transaction], transaction->getTxnType(), Version);
3532
3533 if constexpr (Version > 1)
3534 {
3535 jvTx[jss::tx_json] = jvTx.removeMember(jss::transaction);
3536 jvTx[jss::hash] = hash;
3537 }
3538 else
3539 {
3540 jvTx[jss::transaction][jss::hash] = hash;
3541 }
3542 });
3543
3544 return multiObj;
3545}
3546
3547void
3549 std::shared_ptr<ReadView const> const& ledger,
3550 AcceptedLedgerTx const& transaction,
3551 bool last)
3552{
3553 auto const& stTxn = transaction.getTxn();
3554
3555 // Create two different Json objects, for different API versions
3556 auto const metaRef = std::ref(transaction.getMeta());
3557 auto const trResult = transaction.getResult();
3558 MultiApiJson const jvObj = transJson(stTxn, trResult, true, ledger, metaRef);
3559
3560 {
3561 // Hold each locked subscriber alive until after streamLock_ is
3562 // released; a last-reference ~InfoSub would otherwise re-acquire this
3563 // non-recursive mutex and self-deadlock. Declared before the lock,
3564 // destroyed after the block ends; covers both loops below.
3566
3567 std::scoped_lock const sl(streamLock_);
3568
3569 auto it = streamMaps_[STransactions].begin();
3570 while (it != streamMaps_[STransactions].end())
3571 {
3572 InfoSub::pointer p = it->second.lock();
3573
3574 if (p)
3575 {
3576 jvObj.visit(
3577 p->getApiVersion(), //
3578 [&](json::Value const& jv) { p->send(jv, true); });
3579 toRelease.push_back(std::move(p));
3580 ++it;
3581 }
3582 else
3583 {
3584 it = streamMaps_[STransactions].erase(it);
3585 }
3586 }
3587
3588 it = streamMaps_[SRtTransactions].begin();
3589
3590 while (it != streamMaps_[SRtTransactions].end())
3591 {
3592 InfoSub::pointer p = it->second.lock();
3593
3594 if (p)
3595 {
3596 jvObj.visit(
3597 p->getApiVersion(), //
3598 [&](json::Value const& jv) { p->send(jv, true); });
3599 toRelease.push_back(std::move(p));
3600 ++it;
3601 }
3602 else
3603 {
3604 it = streamMaps_[SRtTransactions].erase(it);
3605 }
3606 }
3607 }
3608
3609 if (transaction.getResult() == tesSUCCESS)
3610 pubBookTransaction(transaction, jvObj);
3611
3612 pubAccountTransaction(ledger, transaction, last);
3613}
3614
3615void
3617{
3618 auto const books = affectedBooks(alTx, journal_);
3619 if (books.empty())
3620 return;
3621
3622 // Two-pass design:
3623 //
3624 // 1. Under bookLock_, walk subBook_, collect a strong pointer for each
3625 // unique listener (and prune any expired weak_ptrs we encounter).
3626 // 2. Release bookLock_, then send to each collected listener.
3627 //
3628 // Reasoning:
3629 // * send() can be slow / blocking, so holding bookLock_ across it would
3630 // stall every other book sub/unsub/pub path on this server (see the
3631 // matching TODO above pubServer at line ~2275).
3632 // * A strong pointer destructed while bookLock_ is held risks running
3633 // ~InfoSub() in-line, which re-enters unsubBook() and mutates the very
3634 // subBook_/SubMapType being iterated -> dangling iterator UB.
3635 //
3636 // Releasing bookLock_ before any InfoSub::pointer can decay solves both.
3637 // ~InfoSub() reacquires bookLock_ via unsubBook() on its own and serializes
3638 // safely with concurrent traffic.
3639
3642
3643 // Sized for the common case where every affected book has at most
3644 // one subscriber. Multi-subscriber books trigger reallocation, but
3645 // that is rare and the upper-bound estimate (sum of per-book sizes)
3646 // would itself require walking subBook_ twice.
3647 listeners.reserve(books.size());
3648 seen.reserve(books.size());
3649
3650 {
3651 std::scoped_lock const sl(bookLock_);
3652
3653 for (auto const& book : books)
3654 {
3655 auto it = subBook_.find(book);
3656 if (it == subBook_.end())
3657 continue;
3658
3659 for (auto sit = it->second.begin(); sit != it->second.end();)
3660 {
3661 if (auto p = sit->second.lock())
3662 {
3663 // Defensive: subBook_ entries are normally cleared by
3664 // ~InfoSub() -> unsubBook(), so we rarely see expired
3665 // weak_ptrs here. The else branch covers the narrow race
3666 // where the last strong ref is dropped between insertion
3667 // and our lock() call.
3668 if (seen.emplace(p->getSeq()).second)
3669 listeners.emplace_back(std::move(p));
3670 ++sit;
3671 }
3672 else
3673 {
3674 JLOG(journal_.debug())
3675 << "pubBookTransaction: pruning expired weak_ptr for seq=" << sit->first;
3676 sit = it->second.erase(sit);
3677 }
3678 }
3679
3680 if (it->second.empty())
3681 subBook_.erase(it);
3682 }
3683 }
3684
3685 for (auto const& p : listeners)
3686 {
3687 jvObj.visit(p->getApiVersion(), [&](json::Value const& jv) { p->send(jv, true); });
3688 }
3689 // listeners destructs here, outside bookLock_; ~InfoSub (if any fires)
3690 // will reacquire bookLock_ via unsubBook with no iterator hazard.
3691}
3692
3693void
3695 std::shared_ptr<ReadView const> const& ledger,
3696 AcceptedLedgerTx const& transaction,
3697 bool last)
3698{
3700 int iProposed = 0;
3701 int iAccepted = 0;
3702
3703 std::vector<SubAccountHistoryInfo> accountHistoryNotify;
3704 auto const currLedgerSeq = ledger->seq();
3705 {
3707
3708 if (!subAccount_.empty() || !subRTAccount_.empty() || !subAccountHistory_.empty())
3709 {
3710 for (auto const& affectedAccount : transaction.getAffected())
3711 {
3712 if (auto simiIt = subRTAccount_.find(affectedAccount);
3713 simiIt != subRTAccount_.end())
3714 {
3715 auto it = simiIt->second.begin();
3716
3717 while (it != simiIt->second.end())
3718 {
3719 InfoSub::pointer const p = it->second.lock();
3720
3721 if (p)
3722 {
3723 notify.insert(p);
3724 ++it;
3725 ++iProposed;
3726 }
3727 else
3728 {
3729 it = simiIt->second.erase(it);
3730 }
3731 }
3732 }
3733
3734 if (auto simiIt = subAccount_.find(affectedAccount); simiIt != subAccount_.end())
3735 {
3736 auto it = simiIt->second.begin();
3737 while (it != simiIt->second.end())
3738 {
3739 InfoSub::pointer const p = it->second.lock();
3740
3741 if (p)
3742 {
3743 notify.insert(p);
3744 ++it;
3745 ++iAccepted;
3746 }
3747 else
3748 {
3749 it = simiIt->second.erase(it);
3750 }
3751 }
3752 }
3753
3754 if (auto historyIt = subAccountHistory_.find(affectedAccount);
3755 historyIt != subAccountHistory_.end())
3756 {
3757 auto& subs = historyIt->second;
3758 auto it = subs.begin();
3759 while (it != subs.end())
3760 {
3761 SubAccountHistoryInfoWeak const& info = it->second;
3762 if (currLedgerSeq <= info.index->separationLedgerSeq)
3763 {
3764 ++it;
3765 continue;
3766 }
3767
3768 if (auto isSptr = info.sinkWptr.lock(); isSptr)
3769 {
3770 accountHistoryNotify.emplace_back(
3771 SubAccountHistoryInfo{.sink = isSptr, .index = info.index});
3772 ++it;
3773 }
3774 else
3775 {
3776 it = subs.erase(it);
3777 }
3778 }
3779 if (subs.empty())
3780 subAccountHistory_.erase(historyIt);
3781 }
3782 }
3783 }
3784 }
3785
3786 JLOG(journal_.trace()) << "pubAccountTransaction: "
3787 << "proposed=" << iProposed << ", accepted=" << iAccepted;
3788
3789 if (!notify.empty() || !accountHistoryNotify.empty())
3790 {
3791 auto const& stTxn = transaction.getTxn();
3792
3793 // Create two different Json objects, for different API versions
3794 auto const metaRef = std::ref(transaction.getMeta());
3795 auto const trResult = transaction.getResult();
3796 MultiApiJson jvObj = transJson(stTxn, trResult, true, ledger, metaRef);
3797
3798 for (InfoSub::ref isrListener : notify)
3799 {
3800 jvObj.visit(
3801 isrListener->getApiVersion(), //
3802 [&](json::Value const& jv) { isrListener->send(jv, true); });
3803 }
3804
3805 if (last)
3806 jvObj.set(jss::account_history_boundary, true);
3807
3808 XRPL_ASSERT(
3809 jvObj.isMember(jss::account_history_tx_stream) == MultiApiJson::IsMemberResult::None,
3810 "xrpl::NetworkOPsImp::pubAccountTransaction : "
3811 "account_history_tx_stream not set");
3812 for (auto& info : accountHistoryNotify)
3813 {
3814 auto& index = info.index;
3815 if (index->forwardTxIndex == 0 && !index->haveHistorical)
3816 jvObj.set(jss::account_history_tx_first, true);
3817
3818 jvObj.set(jss::account_history_tx_index, index->forwardTxIndex++);
3819
3820 jvObj.visit(
3821 info.sink->getApiVersion(), //
3822 [&](json::Value const& jv) { info.sink->send(jv, true); });
3823 }
3824 }
3825}
3826
3827void
3829 std::shared_ptr<ReadView const> const& ledger,
3831 TER result)
3832{
3834 int iProposed = 0;
3835
3836 std::vector<SubAccountHistoryInfo> accountHistoryNotify;
3837
3838 {
3840
3841 if (subRTAccount_.empty())
3842 return;
3843
3844 if (!subAccount_.empty() || !subRTAccount_.empty() || !subAccountHistory_.empty())
3845 {
3846 for (auto const& affectedAccount : tx->getMentionedAccounts())
3847 {
3848 if (auto simiIt = subRTAccount_.find(affectedAccount);
3849 simiIt != subRTAccount_.end())
3850 {
3851 auto it = simiIt->second.begin();
3852
3853 while (it != simiIt->second.end())
3854 {
3855 InfoSub::pointer const p = it->second.lock();
3856
3857 if (p)
3858 {
3859 notify.insert(p);
3860 ++it;
3861 ++iProposed;
3862 }
3863 else
3864 {
3865 it = simiIt->second.erase(it);
3866 }
3867 }
3868 }
3869 }
3870 }
3871 }
3872
3873 JLOG(journal_.trace()) << "pubProposedAccountTransaction: " << iProposed;
3874
3875 if (!notify.empty() || !accountHistoryNotify.empty())
3876 {
3877 // Create two different Json objects, for different API versions
3878 MultiApiJson jvObj = transJson(tx, result, false, ledger, std::nullopt);
3879
3880 for (InfoSub::ref isrListener : notify)
3881 {
3882 jvObj.visit(
3883 isrListener->getApiVersion(), //
3884 [&](json::Value const& jv) { isrListener->send(jv, true); });
3885 }
3886
3887 XRPL_ASSERT(
3888 jvObj.isMember(jss::account_history_tx_stream) == MultiApiJson::IsMemberResult::None,
3889 "xrpl::NetworkOPs::pubProposedAccountTransaction : "
3890 "account_history_tx_stream not set");
3891 for (auto& info : accountHistoryNotify)
3892 {
3893 auto& index = info.index;
3894 if (index->forwardTxIndex == 0 && !index->haveHistorical)
3895 jvObj.set(jss::account_history_tx_first, true);
3896 jvObj.set(jss::account_history_tx_index, index->forwardTxIndex++);
3897 jvObj.visit(
3898 info.sink->getApiVersion(), //
3899 [&](json::Value const& jv) { info.sink->send(jv, true); });
3900 }
3901 }
3902}
3903
3904//
3905// Monitoring
3906//
3907
3908void
3910 InfoSub::ref isrListener,
3911 hash_set<AccountID> const& vnaAccountIDs,
3912 bool rt)
3913{
3914 SubInfoMapType& subMap = rt ? subRTAccount_ : subAccount_;
3915
3916 for (auto const& naAccountID : vnaAccountIDs)
3917 {
3918 JLOG(journal_.trace()) << "subAccount: account: " << toBase58(naAccountID);
3919
3920 isrListener->insertSubAccountInfo(naAccountID, rt);
3921 }
3922
3924
3925 for (auto const& naAccountID : vnaAccountIDs)
3926 {
3927 auto simIterator = subMap.find(naAccountID);
3928 if (simIterator == subMap.end())
3929 {
3930 // Not found, note that account has a new single listener.
3931 SubMapType usisElement;
3932 usisElement[isrListener->getSeq()] = isrListener;
3933 // VFALCO NOTE This is making a needless copy of naAccountID
3934 subMap.insert(simIterator, make_pair(naAccountID, usisElement));
3935 }
3936 else
3937 {
3938 // Found, note that the account has another listener.
3939 simIterator->second[isrListener->getSeq()] = isrListener;
3940 }
3941 }
3942}
3943
3944void
3946 InfoSub::ref isrListener,
3947 hash_set<AccountID> const& vnaAccountIDs,
3948 bool rt)
3949{
3950 for (auto const& naAccountID : vnaAccountIDs)
3951 {
3952 // Remove from the InfoSub
3953 isrListener->deleteSubAccountInfo(naAccountID, rt);
3954 }
3955
3956 // Remove from the server
3957 unsubAccountInternal(isrListener->getSeq(), vnaAccountIDs, rt);
3958}
3959
3960void
3962 std::uint64_t uSeq,
3963 hash_set<AccountID> const& vnaAccountIDs,
3964 bool rt)
3965{
3967
3968 SubInfoMapType& subMap = rt ? subRTAccount_ : subAccount_;
3969
3970 for (auto const& naAccountID : vnaAccountIDs)
3971 {
3972 auto simIterator = subMap.find(naAccountID);
3973
3974 if (simIterator != subMap.end())
3975 {
3976 // Found
3977 simIterator->second.erase(uSeq);
3978
3979 if (simIterator->second.empty())
3980 {
3981 // Don't need hash entry.
3982 subMap.erase(simIterator);
3983 }
3984 }
3985 }
3986}
3987
3988template <typename OuterMap, typename BeforeErase>
3989void
3991 std::uint64_t seq,
3992 hash_set<AccountID> const& accounts,
3993 OuterMap& outerMap,
3994 BeforeErase&& beforeErase)
3995{
3996 // Walk the disconnecting connection's accounts in chunks. Each chunk takes
3997 // accountLock_, erases up to kAccountCleanupChunk entries, then releases
3998 // the lock so a competing account-publish can run before the next chunk.
3999 // No iterator into outerMap is held across the unlock: every chunk re-finds
4000 // each account, so a concurrent mutation between chunks cannot dangle.
4001 auto it = accounts.begin();
4002 auto const end = accounts.end();
4003 while (it != end)
4004 {
4006
4007 for (std::size_t n = 0; n < kAccountCleanupChunk && it != end; ++n, ++it)
4008 {
4009 auto outerIter = outerMap.find(*it);
4010 if (outerIter != outerMap.end())
4011 {
4012 // Give the caller a chance to tear down this connection's inner
4013 // entry before it is erased (the history map stops its paging
4014 // job here); the plain account maps pass a no-op.
4015 auto innerIter = outerIter->second.find(seq);
4016 if (innerIter != outerIter->second.end())
4017 beforeErase(innerIter->second);
4018
4019 // Erase only this connection's seq; other connections sharing
4020 // the account keep their entry, so a reconnect is unaffected.
4021 outerIter->second.erase(seq);
4022 if (outerIter->second.empty())
4023 outerMap.erase(outerIter);
4024 }
4025 }
4026 }
4027}
4028
4029void
4031 std::uint64_t seq,
4032 hash_set<AccountID> const& accounts,
4033 SubInfoMapType& subMap)
4034{
4035 // Plain account maps need no per-entry teardown before erase.
4036 cleanupSubscriptionMap(seq, accounts, subMap, [](InfoSub::wptr const&) {});
4037}
4038
4039void
4041 std::uint64_t seq,
4042 hash_set<AccountID> const& accounts)
4043{
4044 // Cancel any in-flight historical paging job for this connection before
4045 // dropping its record. The job holds its own shared_ptr to the index, so
4046 // erasing the map entry alone would not stop it; it reads this atomic
4047 // between pages and exits promptly once set.
4049 seq, accounts, subAccountHistory_, [](SubAccountHistoryInfoWeak const& info) {
4050 info.index->stopHistorical = true;
4051 });
4052}
4053
4054void
4056 std::uint64_t seq,
4057 hash_set<AccountID> rtAccounts,
4058 hash_set<AccountID> normalAccounts,
4059 hash_set<AccountID> historyAccounts)
4060{
4061 // Nothing to do for a connection that never subscribed to any account.
4062 if (rtAccounts.empty() && normalAccounts.empty() && historyAccounts.empty())
4063 return;
4064
4065 // Post the erase work to a low-priority job so the disconnect thread (and
4066 // ~InfoSub) returns immediately. The job captures the sets BY MOVE and
4067 // operates purely on seq + the captured accounts; it never touches the
4068 // destroyed InfoSub. `this` outlives the job per the Source lifetime
4069 // contract. Running on a JobQueue thread, it cannot re-enter accountLock_
4070 // held by the disconnecting thread, so the plain std::mutex is safe.
4071 //
4072 // The body is exception-guarded: the JobQueue invokes it bare, so an
4073 // escaping exception on the worker thread would terminate the process.
4074 //
4075 // addJob returns false only once the JobQueue has been stopped, i.e. during
4076 // process shutdown. At that point NetworkOPsImp's maps are about to be
4077 // destroyed wholesale and no publish path can run, so dropping the cleanup
4078 // is harmless; no inline fallback is needed.
4079 jobQueue_.addJob(
4081 "SubCleanup",
4082 [this,
4083 seq,
4084 rt = std::move(rtAccounts),
4085 normal = std::move(normalAccounts),
4086 history = std::move(historyAccounts)]() noexcept {
4087 try
4088 {
4092 }
4093 catch (std::exception const& e)
4094 {
4095 JLOG(journal_.error()) << "SubCleanup[seq=" << seq << "]: " << e.what();
4096 }
4097 catch (...)
4098 {
4099 JLOG(journal_.error()) << "SubCleanup[seq=" << seq << "]: unknown exception";
4100 }
4101 });
4102}
4103
4104void
4106{
4107 registry_.get().getJobQueue().addJob(JtClientAcctHist, "HistTxStream", [this, subInfo]() {
4108 auto const& accountId = subInfo.index->accountId;
4109 auto& lastLedgerSeq = subInfo.index->historyLastLedgerSeq;
4110 auto& txHistoryIndex = subInfo.index->historyTxIndex;
4111
4112 JLOG(journal_.trace()) << "AccountHistory job for account " << toBase58(accountId)
4113 << " started. lastLedgerSeq=" << lastLedgerSeq;
4114
4115 auto isFirstTx = [&](std::shared_ptr<Transaction> const& tx,
4116 std::shared_ptr<TxMeta> const& meta) -> bool {
4117 /*
4118 * genesis account: first tx is the one with seq 1
4119 * other account: first tx is the one created the account
4120 */
4121 if (accountId == kGenesisAccountId)
4122 {
4123 auto stx = tx->getSTransaction();
4124 if (stx->getAccountID(sfAccount) == accountId && stx->getSeqProxy().value() == 1)
4125 return true;
4126 }
4127
4128 return std::ranges::any_of(meta->getNodes(), [&](auto& node) {
4129 if (node.getFieldU16(sfLedgerEntryType) != ltACCOUNT_ROOT)
4130 return false;
4131
4132 if (node.isFieldPresent(sfNewFields))
4133 {
4134 if (auto inner = dynamic_cast<STObject const*>(node.peekAtPField(sfNewFields));
4135 inner)
4136 {
4137 if (inner->isFieldPresent(sfAccount) &&
4138 inner->getAccountID(sfAccount) == accountId)
4139 {
4140 return true;
4141 }
4142 }
4143 }
4144 return false;
4145 });
4146 };
4147
4148 auto send = [&](json::Value const& jvObj, bool unsubscribe) -> bool {
4149 if (auto sptr = subInfo.sinkWptr.lock())
4150 {
4151 sptr->send(jvObj, true);
4152 if (unsubscribe)
4153 unsubAccountHistory(sptr, accountId, false);
4154 return true;
4155 }
4156
4157 return false;
4158 };
4159
4160 auto sendMultiApiJson = [&](MultiApiJson const& jvObj, bool unsubscribe) -> bool {
4161 if (auto sptr = subInfo.sinkWptr.lock())
4162 {
4163 jvObj.visit(
4164 sptr->getApiVersion(), //
4165 [&](json::Value const& jv) { sptr->send(jv, true); });
4166
4167 if (unsubscribe)
4168 unsubAccountHistory(sptr, accountId, false);
4169 return true;
4170 }
4171
4172 return false;
4173 };
4174
4175 auto getMoreTxns = [&](std::uint32_t minLedger,
4176 std::uint32_t maxLedger,
4178 -> std::pair<
4181 auto& db = registry_.get().getRelationalDatabase();
4183 .account = accountId,
4184 .ledgerRange = {.min = minLedger, .max = maxLedger},
4185 .marker = marker,
4186 .limit = 0,
4187 .bAdmin = true,
4188 .delegate = std::nullopt};
4189 return db.newestAccountTxPage(options);
4190 };
4191
4192 /*
4193 * search backward until the genesis ledger or asked to stop
4194 */
4195 while (lastLedgerSeq >= 2 && !subInfo.index->stopHistorical)
4196 {
4197 int feeChargeCount = 0;
4198 if (auto sptr = subInfo.sinkWptr.lock(); sptr)
4199 {
4200 sptr->getConsumer().charge(resource::kFeeMediumBurdenRpc);
4201 ++feeChargeCount;
4202 }
4203 else
4204 {
4205 JLOG(journal_.trace())
4206 << "AccountHistory job for account " << toBase58(accountId)
4207 << " no InfoSub. Fee charged " << feeChargeCount << " times.";
4208 return;
4209 }
4210
4211 // try to search in 1024 ledgers till reaching genesis ledgers
4212 auto startLedgerSeq = (lastLedgerSeq > 1024 + 2 ? lastLedgerSeq - 1024 : 2);
4213 JLOG(journal_.trace())
4214 << "AccountHistory job for account " << toBase58(accountId)
4215 << ", working on ledger range [" << startLedgerSeq << "," << lastLedgerSeq << "]";
4216
4217 auto haveRange = [&]() -> bool {
4218 std::uint32_t validatedMin = UINT_MAX;
4219 std::uint32_t validatedMax = 0;
4220 auto haveSomeValidatedLedgers =
4221 registry_.get().getLedgerMaster().getValidatedRange(validatedMin, validatedMax);
4222
4223 return haveSomeValidatedLedgers && validatedMin <= startLedgerSeq &&
4224 lastLedgerSeq <= validatedMax;
4225 }();
4226
4227 if (!haveRange)
4228 {
4229 JLOG(journal_.debug()) << "AccountHistory reschedule job for account "
4230 << toBase58(accountId) << ", incomplete ledger range ["
4231 << startLedgerSeq << "," << lastLedgerSeq << "]";
4233 return;
4234 }
4235
4237 while (!subInfo.index->stopHistorical)
4238 {
4239 auto dbResult = getMoreTxns(startLedgerSeq, lastLedgerSeq, marker);
4240
4241 auto const& txns = dbResult.first;
4242 marker = dbResult.second;
4243 size_t const numTxns = txns.size();
4244 for (size_t i = 0; i < numTxns; ++i)
4245 {
4246 auto const& [tx, meta] = txns[i];
4247
4248 if (!tx || !meta)
4249 {
4250 JLOG(journal_.debug()) << "AccountHistory job for account "
4251 << toBase58(accountId) << " empty tx or meta.";
4252 send(rpcError(RpcInternal), true);
4253 return;
4254 }
4255 auto curTxLedger =
4256 registry_.get().getLedgerMaster().getLedgerBySeq(tx->getLedger());
4257 if (!curTxLedger)
4258 {
4259 // LCOV_EXCL_START
4260 UNREACHABLE(
4261 "xrpl::NetworkOPsImp::addAccountHistoryJob : "
4262 "getLedgerBySeq failed");
4263 JLOG(journal_.debug()) << "AccountHistory job for account "
4264 << toBase58(accountId) << " no ledger.";
4265 send(rpcError(RpcInternal), true);
4266 return;
4267 // LCOV_EXCL_STOP
4268 }
4269 std::shared_ptr<STTx const> const stTxn = tx->getSTransaction();
4270 if (!stTxn)
4271 {
4272 // LCOV_EXCL_START
4273 UNREACHABLE(
4274 "NetworkOPsImp::addAccountHistoryJob : "
4275 "getSTransaction failed");
4276 JLOG(journal_.debug()) << "AccountHistory job for account "
4277 << toBase58(accountId) << " getSTransaction failed.";
4278 send(rpcError(RpcInternal), true);
4279 return;
4280 // LCOV_EXCL_STOP
4281 }
4282
4283 auto const ref = std::ref(*meta);
4284 auto const trR = meta->getResultTER();
4285 MultiApiJson jvTx = transJson(stTxn, trR, true, curTxLedger, ref);
4286
4287 jvTx.set(jss::account_history_tx_index, txHistoryIndex--);
4288 if (i + 1 == numTxns || txns[i + 1].first->getLedger() != tx->getLedger())
4289 jvTx.set(jss::account_history_boundary, true);
4290
4291 if (isFirstTx(tx, meta))
4292 {
4293 jvTx.set(jss::account_history_tx_first, true);
4294 sendMultiApiJson(jvTx, false);
4295
4296 JLOG(journal_.trace()) << "AccountHistory job for account "
4297 << toBase58(accountId) << " done, found last tx.";
4298 return;
4299 }
4300
4301 sendMultiApiJson(jvTx, false);
4302 }
4303
4304 if (marker)
4305 {
4306 JLOG(journal_.trace())
4307 << "AccountHistory job for account " << toBase58(accountId)
4308 << " paging, marker=" << marker->ledgerSeq << ":" << marker->txnSeq;
4309 }
4310 else
4311 {
4312 break;
4313 }
4314 }
4315
4316 if (!subInfo.index->stopHistorical)
4317 {
4318 lastLedgerSeq = startLedgerSeq - 1;
4319 if (lastLedgerSeq <= 1)
4320 {
4321 JLOG(journal_.trace())
4322 << "AccountHistory job for account " << toBase58(accountId)
4323 << " done, reached genesis ledger.";
4324 return;
4325 }
4326 }
4327 }
4328 });
4329}
4330
4331void
4333 std::shared_ptr<ReadView const> const& ledger,
4335{
4336 subInfo.index->separationLedgerSeq = ledger->seq();
4337 auto const& accountId = subInfo.index->accountId;
4338 auto const accountKeylet = keylet::account(accountId);
4339 if (!ledger->exists(accountKeylet))
4340 {
4341 JLOG(journal_.debug()) << "subAccountHistoryStart, no account " << toBase58(accountId)
4342 << ", no need to add AccountHistory job.";
4343 return;
4344 }
4345 if (accountId == kGenesisAccountId)
4346 {
4347 if (auto const sleAcct = ledger->read(accountKeylet); sleAcct)
4348 {
4349 if (sleAcct->getFieldU32(sfSequence) == 1)
4350 {
4351 JLOG(journal_.debug())
4352 << "subAccountHistoryStart, genesis account " << toBase58(accountId)
4353 << " does not have tx, no need to add AccountHistory job.";
4354 return;
4355 }
4356 }
4357 else
4358 {
4359 // LCOV_EXCL_START
4360 UNREACHABLE(
4361 "xrpl::NetworkOPsImp::subAccountHistoryStart : failed to "
4362 "access genesis account");
4363 return;
4364 // LCOV_EXCL_STOP
4365 }
4366 }
4367 subInfo.index->historyLastLedgerSeq = ledger->seq();
4368 subInfo.index->haveHistorical = true;
4369
4370 JLOG(journal_.debug()) << "subAccountHistoryStart, add AccountHistory job: accountId="
4371 << toBase58(accountId) << ", currentLedgerSeq=" << ledger->seq();
4372
4373 addAccountHistoryJob(subInfo);
4374}
4375
4378{
4379 if (!isrListener->insertSubAccountHistory(accountId))
4380 {
4381 JLOG(journal_.debug()) << "subAccountHistory, already subscribed to account "
4382 << toBase58(accountId);
4383 return RpcInvalidParams;
4384 }
4385
4388 .sinkWptr = isrListener, .index = std::make_shared<SubAccountHistoryIndex>(accountId)};
4389 auto simIterator = subAccountHistory_.find(accountId);
4390 if (simIterator == subAccountHistory_.end())
4391 {
4393 inner.emplace(isrListener->getSeq(), ahi);
4394 subAccountHistory_.insert(simIterator, std::make_pair(accountId, inner));
4395 }
4396 else
4397 {
4398 simIterator->second.emplace(isrListener->getSeq(), ahi);
4399 }
4400
4401 auto const ledger = registry_.get().getLedgerMaster().getValidatedLedger();
4402 if (ledger)
4403 {
4404 subAccountHistoryStart(ledger, ahi);
4405 }
4406 else
4407 {
4408 // The node does not have validated ledgers, so wait for
4409 // one before start streaming.
4410 // In this case, the subscription is also considered successful.
4411 JLOG(journal_.debug()) << "subAccountHistory, no validated ledger yet, delay start";
4412 }
4413
4414 return RpcSuccess;
4415}
4416
4417void
4419 InfoSub::ref isrListener,
4420 AccountID const& account,
4421 bool historyOnly)
4422{
4423 if (!historyOnly)
4424 isrListener->deleteSubAccountHistory(account);
4425 unsubAccountHistoryInternal(isrListener->getSeq(), account, historyOnly);
4426}
4427
4428void
4430 std::uint64_t seq,
4431 AccountID const& account,
4432 bool historyOnly)
4433{
4435 auto simIterator = subAccountHistory_.find(account);
4436 if (simIterator != subAccountHistory_.end())
4437 {
4438 auto& subInfoMap = simIterator->second;
4439 auto subInfoIter = subInfoMap.find(seq);
4440 if (subInfoIter != subInfoMap.end())
4441 {
4442 subInfoIter->second.index->stopHistorical = true;
4443 }
4444
4445 if (!historyOnly)
4446 {
4447 simIterator->second.erase(seq);
4448 if (simIterator->second.empty())
4449 {
4450 subAccountHistory_.erase(simIterator);
4451 }
4452 }
4453 JLOG(journal_.debug()) << "unsubAccountHistory, account " << toBase58(account)
4454 << ", historyOnly = " << (historyOnly ? "true" : "false");
4455 }
4456}
4457
4458bool
4460{
4461 // Server-side insert first, then InfoSub bookkeeping. If the InfoSub-side
4462 // insert throws, the orphan in subBook_ is cleared by the expired-weak_ptr
4463 // prune in pubBookTransaction. With the reverse ordering, ~InfoSub would
4464 // call unsubBookInternal for a key that was never inserted server-side.
4465 {
4466 std::scoped_lock const sl(bookLock_);
4467 subBook_[book].try_emplace(isrListener->getSeq(), isrListener);
4468 }
4469 isrListener->insertBookSubscription(book);
4470 return true;
4471}
4472
4473bool
4475{
4476 // Mirrors unsubAccount: clear the per-subscriber tracking set first so
4477 // ~InfoSub does not re-issue an unsubBookInternal for a book the caller
4478 // already removed, then erase the server-side entry.
4479 isrListener->deleteBookSubscription(book);
4480 return unsubBookInternal(isrListener->getSeq(), book);
4481}
4482
4483bool
4485{
4486 std::scoped_lock const sl(bookLock_);
4487 auto it = subBook_.find(book);
4488 if (it == subBook_.end())
4489 return false;
4490 bool const erased = it->second.erase(uSeq) != 0u;
4491 if (it->second.empty())
4492 subBook_.erase(it);
4493 return erased;
4494}
4495
4498{
4499 // This code-path is exclusively used when the server is in standalone
4500 // mode via `ledger_accept`
4501 XRPL_ASSERT(standalone_, "xrpl::NetworkOPsImp::acceptLedger : is standalone");
4502
4503 if (!standalone_)
4504 Throw<std::runtime_error>("Operation only possible in STANDALONE mode.");
4505
4506 // FIXME Could we improve on this and remove the need for a specialized
4507 // API in Consensus?
4508 beginConsensus(ledgerMaster_.getClosedLedger()->header().hash, {});
4509 consensus_.simulate(registry_.get().getTimeKeeper().closeTime(), consensusDelay);
4510 return ledgerMaster_.getCurrentLedger()->header().seq;
4511}
4512
4513// <-- bool: true=added, false=already there
4514bool
4516{
4517 if (auto lpClosed = ledgerMaster_.getValidatedLedger())
4518 {
4519 jvResult[jss::ledger_index] = lpClosed->header().seq;
4520 jvResult[jss::ledger_hash] = to_string(lpClosed->header().hash);
4521 jvResult[jss::ledger_time] =
4522 json::Value::UInt(lpClosed->header().closeTime.time_since_epoch().count());
4523 if (!lpClosed->rules().enabled(featureXRPFees))
4524 jvResult[jss::fee_ref] = kFeeUnitsDeprecated;
4525 jvResult[jss::fee_base] = lpClosed->fees().base.jsonClipped();
4526 jvResult[jss::reserve_base] = lpClosed->fees().reserve.jsonClipped();
4527 jvResult[jss::reserve_inc] = lpClosed->fees().increment.jsonClipped();
4528 jvResult[jss::network_id] = registry_.get().getNetworkIDService().getNetworkID();
4529 }
4530
4532 {
4533 jvResult[jss::validated_ledgers] = registry_.get().getLedgerMaster().getCompleteLedgers();
4534 }
4535
4536 std::scoped_lock const sl(streamLock_);
4537 return streamMaps_[SLedger].emplace(isrListener->getSeq(), isrListener).second;
4538}
4539
4540// <-- bool: true=added, false=already there
4541bool
4543{
4544 std::scoped_lock const sl(streamLock_);
4545 return streamMaps_[SBookChanges].emplace(isrListener->getSeq(), isrListener).second;
4546}
4547
4548// <-- bool: true=erased, false=was not there
4549bool
4551{
4552 std::scoped_lock const sl(streamLock_);
4553 return streamMaps_[SLedger].erase(uSeq) != 0u;
4554}
4555
4556// <-- bool: true=erased, false=was not there
4557bool
4559{
4560 std::scoped_lock const sl(streamLock_);
4561 return streamMaps_[SBookChanges].erase(uSeq) != 0u;
4562}
4563
4564// <-- bool: true=added, false=already there
4565bool
4567{
4568 std::scoped_lock const sl(streamLock_);
4569 return streamMaps_[SManifests].emplace(isrListener->getSeq(), isrListener).second;
4570}
4571
4572// <-- bool: true=erased, false=was not there
4573bool
4575{
4576 std::scoped_lock const sl(streamLock_);
4577 return streamMaps_[SManifests].erase(uSeq) != 0u;
4578}
4579
4580// <-- bool: true=added, false=already there
4581bool
4582NetworkOPsImp::subServer(InfoSub::ref isrListener, json::Value& jvResult, bool admin)
4583{
4584 uint256 uRandom;
4585
4586 if (standalone_)
4587 jvResult[jss::stand_alone] = standalone_;
4588
4589 // CHECKME: is it necessary to provide a random number here?
4590 beast::rngfill(uRandom.begin(), uRandom.size(), cryptoPrng());
4591
4592 auto const& feeTrack = registry_.get().getFeeTrack();
4593 jvResult[jss::random] = to_string(uRandom);
4594 jvResult[jss::server_status] = strOperatingMode(admin);
4595 jvResult[jss::load_base] = feeTrack.getLoadBase();
4596 jvResult[jss::load_factor] = feeTrack.getLoadFactor();
4597 jvResult[jss::hostid] = getHostId(admin);
4598 jvResult[jss::pubkey_node] =
4599 toBase58(TokenType::NodePublic, registry_.get().getApp().nodeIdentity().first);
4600
4601 std::scoped_lock const sl(streamLock_);
4602 return streamMaps_[SServer].emplace(isrListener->getSeq(), isrListener).second;
4603}
4604
4605// <-- bool: true=erased, false=was not there
4606bool
4608{
4609 std::scoped_lock const sl(streamLock_);
4610 return streamMaps_[SServer].erase(uSeq) != 0u;
4611}
4612
4613// <-- bool: true=added, false=already there
4614bool
4616{
4617 std::scoped_lock const sl(streamLock_);
4618 return streamMaps_[STransactions].emplace(isrListener->getSeq(), isrListener).second;
4619}
4620
4621// <-- bool: true=erased, false=was not there
4622bool
4624{
4625 std::scoped_lock const sl(streamLock_);
4626 return streamMaps_[STransactions].erase(uSeq) != 0u;
4627}
4628
4629// <-- bool: true=added, false=already there
4630bool
4632{
4633 std::scoped_lock const sl(streamLock_);
4634 return streamMaps_[SRtTransactions].emplace(isrListener->getSeq(), isrListener).second;
4635}
4636
4637// <-- bool: true=erased, false=was not there
4638bool
4640{
4641 std::scoped_lock const sl(streamLock_);
4642 return streamMaps_[SRtTransactions].erase(uSeq) != 0u;
4643}
4644
4645// <-- bool: true=added, false=already there
4646bool
4648{
4649 std::scoped_lock const sl(streamLock_);
4650 return streamMaps_[SValidations].emplace(isrListener->getSeq(), isrListener).second;
4651}
4652
4653void
4658
4659// <-- bool: true=erased, false=was not there
4660bool
4662{
4663 std::scoped_lock const sl(streamLock_);
4664 return streamMaps_[SValidations].erase(uSeq) != 0u;
4665}
4666
4667// <-- bool: true=added, false=already there
4668bool
4670{
4671 std::scoped_lock const sl(streamLock_);
4672 return streamMaps_[SPeerStatus].emplace(isrListener->getSeq(), isrListener).second;
4673}
4674
4675// <-- bool: true=erased, false=was not there
4676bool
4678{
4679 std::scoped_lock const sl(streamLock_);
4680 return streamMaps_[SPeerStatus].erase(uSeq) != 0u;
4681}
4682
4683// <-- bool: true=added, false=already there
4684bool
4686{
4687 std::scoped_lock const sl(streamLock_);
4688 return streamMaps_[SConsensusPhase].emplace(isrListener->getSeq(), isrListener).second;
4689}
4690
4691// <-- bool: true=erased, false=was not there
4692bool
4694{
4695 std::scoped_lock const sl(streamLock_);
4696 return streamMaps_[SConsensusPhase].erase(uSeq) != 0u;
4697}
4698
4701{
4702 // Caller already holds streamLock_; this performs the lookup only.
4703 auto const it = rpcSubMap_.find(strUrl);
4704
4705 if (it != rpcSubMap_.end())
4706 return it->second;
4707
4708 return InfoSub::pointer();
4709}
4710
4713{
4714 std::scoped_lock const sl(streamLock_);
4715 return findRpcSubLocked(strUrl);
4716}
4717
4720{
4721 std::scoped_lock const sl(streamLock_);
4722
4723 rpcSubMap_.emplace(strUrl, rspEntry);
4724
4725 return rspEntry;
4726}
4727
4728bool
4730{
4731 // Declared before the lock so it outlives the scoped_lock and is destroyed
4732 // only after streamLock_ is released. The erase below may drop the last
4733 // strong reference; if so, ~InfoSub runs and its unsub* calls re-acquire
4734 // the non-recursive streamLock_. Destroying pInfo inside the lock would
4735 // self-deadlock.
4736 InfoSub::pointer pInfo;
4737 {
4738 std::scoped_lock const sl(streamLock_);
4739 // Use the no-lock helper: we already hold streamLock_ and the mutex is
4740 // not recursive, so calling the public findRpcSub here would deadlock.
4741 pInfo = findRpcSubLocked(strUrl);
4742
4743 if (!pInfo)
4744 return false;
4745
4746 // check to see if any of the stream maps still hold a weak reference to
4747 // this entry before removing
4748 for (SubMapType const& map : streamMaps_)
4749 {
4750 if (map.contains(pInfo->getSeq()))
4751 return false;
4752 }
4753 rpcSubMap_.erase(strUrl);
4754 }
4755 // pInfo destroyed here, after streamLock_ is released.
4756 return true;
4757}
4758
4759#ifndef USE_NEW_BOOK_PAGE
4760
4761// NIKB FIXME this should be looked at. There's no reason why this shouldn't
4762// work, but it demonstrated poor performance.
4763//
4764void
4767 Book const& book,
4768 AccountID const& uTakerID,
4769 bool const bProof,
4770 unsigned int iLimit,
4771 json::Value const& jvMarker,
4772 json::Value& jvResult)
4773{ // CAUTION: This is the old get book page logic
4774 json::Value& jvOffers = (jvResult[jss::offers] = json::Value(json::ValueType::Array));
4775
4777 uint256 const uBookBase = getBookBase(book);
4778 uint256 const uBookEnd = getQualityNext(uBookBase);
4779 uint256 uTipIndex = uBookBase;
4780
4781 if (auto stream = journal_.trace())
4782 {
4783 stream << "getBookPage:" << book;
4784 stream << "getBookPage: uBookBase=" << uBookBase;
4785 stream << "getBookPage: uBookEnd=" << uBookEnd;
4786 stream << "getBookPage: uTipIndex=" << uTipIndex;
4787 }
4788
4789 ReadView const& view = *lpLedger;
4790
4791 bool const bGlobalFreeze =
4792 isGlobalFrozen(view, book.out.getIssuer()) || isGlobalFrozen(view, book.in.getIssuer());
4793
4794 bool bDone = false;
4795 bool bDirectAdvance = true;
4796
4797 SLE::const_pointer sleOfferDir;
4798 uint256 offerIndex;
4799 unsigned int uBookEntry = 0;
4800 STAmount saDirRate;
4801
4802 auto const rate = transferRate(view, book.out.getIssuer());
4803 auto viewJ = registry_.get().getJournal("View");
4804
4805 while (!bDone && iLimit-- > 0)
4806 {
4807 if (bDirectAdvance)
4808 {
4809 bDirectAdvance = false;
4810
4811 JLOG(journal_.trace()) << "getBookPage: bDirectAdvance";
4812
4813 auto const ledgerIndex = view.succ(uTipIndex, uBookEnd);
4814 if (ledgerIndex)
4815 {
4816 sleOfferDir = view.read(keylet::page(*ledgerIndex));
4817 }
4818 else
4819 {
4820 sleOfferDir.reset();
4821 }
4822
4823 if (!sleOfferDir)
4824 {
4825 JLOG(journal_.trace()) << "getBookPage: bDone";
4826 bDone = true;
4827 }
4828 else
4829 {
4830 uTipIndex = sleOfferDir->key();
4831 saDirRate = amountFromQuality(getQuality(uTipIndex));
4832
4833 cdirFirst(view, uTipIndex, sleOfferDir, uBookEntry, offerIndex);
4834
4835 JLOG(journal_.trace()) << "getBookPage: uTipIndex=" << uTipIndex;
4836 JLOG(journal_.trace()) << "getBookPage: offerIndex=" << offerIndex;
4837 }
4838 }
4839
4840 if (!bDone)
4841 {
4842 auto sleOffer = view.read(keylet::offer(offerIndex));
4843
4844 if (sleOffer)
4845 {
4846 auto const uOfferOwnerID = sleOffer->getAccountID(sfAccount);
4847 auto const& saTakerGets = sleOffer->getFieldAmount(sfTakerGets);
4848 auto const& saTakerPays = sleOffer->getFieldAmount(sfTakerPays);
4849 STAmount saOwnerFunds;
4850 bool firstOwnerOffer(true);
4851
4852 if (book.out.getIssuer() == uOfferOwnerID)
4853 {
4854 // If an offer is selling issuer's own IOUs, it is fully
4855 // funded.
4856 saOwnerFunds = saTakerGets;
4857 }
4858 else if (bGlobalFreeze)
4859 {
4860 // If either asset is globally frozen, consider all offers
4861 // that aren't ours to be totally unfunded
4862 saOwnerFunds.clear(book.out);
4863 }
4864 else
4865 {
4866 auto umBalanceEntry = umBalance.find(uOfferOwnerID);
4867 if (umBalanceEntry != umBalance.end())
4868 {
4869 // Found in running balance table.
4870
4871 saOwnerFunds = umBalanceEntry->second;
4872 firstOwnerOffer = false;
4873 }
4874 else
4875 {
4876 // Did not find balance in table.
4877
4878 saOwnerFunds = accountHolds(
4879 view,
4880 uOfferOwnerID,
4881 book.out,
4884 viewJ);
4885
4886 if (saOwnerFunds < beast::kZero)
4887 {
4888 // Treat negative funds as zero.
4889
4890 saOwnerFunds.clear();
4891 }
4892 }
4893 }
4894
4895 json::Value jvOffer = sleOffer->getJson(JsonOptions::Values::None);
4896
4897 STAmount saTakerGetsFunded;
4898 STAmount saOwnerFundsLimit = saOwnerFunds;
4899 Rate offerRate = kParityRate;
4900
4901 if (rate != kParityRate
4902 // Have a transfer fee.
4903 && uTakerID != book.out.getIssuer()
4904 // Not taking offers of own IOUs.
4905 && book.out.getIssuer() != uOfferOwnerID)
4906 // Offer owner not issuing ownfunds
4907 {
4908 // Need to charge a transfer fee to offer owner.
4909 offerRate = rate;
4910 saOwnerFundsLimit = divide(saOwnerFunds, offerRate);
4911 }
4912
4913 if (saOwnerFundsLimit >= saTakerGets)
4914 {
4915 // Sufficient funds no shenanigans.
4916 saTakerGetsFunded = saTakerGets;
4917 }
4918 else
4919 {
4920 // Only provide, if not fully funded.
4921
4922 saTakerGetsFunded = saOwnerFundsLimit;
4923
4924 saTakerGetsFunded.setJson(jvOffer[jss::taker_gets_funded]);
4925 std::min(
4926 saTakerPays, multiply(saTakerGetsFunded, saDirRate, saTakerPays.asset()))
4927 .setJson(jvOffer[jss::taker_pays_funded]);
4928 }
4929
4930 STAmount const saOwnerPays = (kParityRate == offerRate)
4931 ? saTakerGetsFunded
4932 : std::min(saOwnerFunds, multiply(saTakerGetsFunded, offerRate));
4933
4934 umBalance[uOfferOwnerID] = saOwnerFunds - saOwnerPays;
4935
4936 // Include all offers funded and unfunded
4937 json::Value& jvOf = jvOffers.append(jvOffer);
4938 jvOf[jss::quality] = saDirRate.getText();
4939
4940 if (firstOwnerOffer)
4941 jvOf[jss::owner_funds] = saOwnerFunds.getText();
4942 }
4943 else
4944 {
4945 JLOG(journal_.warn()) << "Missing offer";
4946 }
4947
4948 if (!cdirNext(view, uTipIndex, sleOfferDir, uBookEntry, offerIndex))
4949 {
4950 bDirectAdvance = true;
4951 }
4952 else
4953 {
4954 JLOG(journal_.trace()) << "getBookPage: offerIndex=" << offerIndex;
4955 }
4956 }
4957 }
4958
4959 // jvResult[jss::marker] = json::Value(json::ValueType::Array);
4960 // jvResult[jss::nodes] = json::Value(json::ValueType::Array);
4961}
4962
4963#else
4964
4965// This is the new code that uses the book iterators
4966// It has temporarily been disabled
4967
4968void
4971 Book const& book,
4972 AccountID const& uTakerID,
4973 bool const bProof,
4974 unsigned int iLimit,
4975 json::Value const& jvMarker,
4976 json::Value& jvResult)
4977{
4978 auto& jvOffers = (jvResult[jss::offers] = json::Value(json::ValueType::Array));
4979
4981
4982 MetaView lesActive(lpLedger, tapNONE, true);
4983 OrderBookIterator obIterator(lesActive, book);
4984
4985 auto const rate = transferRate(lesActive, book.out.account);
4986
4987 bool const bGlobalFreeze =
4988 lesActive.isGlobalFrozen(book.out.account) || lesActive.isGlobalFrozen(book.in.account);
4989
4990 while (iLimit-- > 0 && obIterator.nextOffer())
4991 {
4992 SLE::pointer sleOffer = obIterator.getCurrentOffer();
4993 if (sleOffer)
4994 {
4995 auto const uOfferOwnerID = sleOffer->getAccountID(sfAccount);
4996 auto const& saTakerGets = sleOffer->getFieldAmount(sfTakerGets);
4997 auto const& saTakerPays = sleOffer->getFieldAmount(sfTakerPays);
4998 STAmount saDirRate = obIterator.getCurrentRate();
4999 STAmount saOwnerFunds;
5000
5001 if (book.out.account == uOfferOwnerID)
5002 {
5003 // If offer is selling issuer's own IOUs, it is fully funded.
5004 saOwnerFunds = saTakerGets;
5005 }
5006 else if (bGlobalFreeze)
5007 {
5008 // If either asset is globally frozen, consider all offers
5009 // that aren't ours to be totally unfunded
5010 saOwnerFunds.clear(book.out);
5011 }
5012 else
5013 {
5014 auto umBalanceEntry = umBalance.find(uOfferOwnerID);
5015
5016 if (umBalanceEntry != umBalance.end())
5017 {
5018 // Found in running balance table.
5019
5020 saOwnerFunds = umBalanceEntry->second;
5021 }
5022 else
5023 {
5024 // Did not find balance in table.
5025
5026 saOwnerFunds = lesActive.accountHolds(
5027 uOfferOwnerID,
5028 book.out.currency,
5029 book.out.account,
5031
5032 if (saOwnerFunds.isNegative())
5033 {
5034 // Treat negative funds as zero.
5035
5036 saOwnerFunds.zero();
5037 }
5038 }
5039 }
5040
5041 json::Value jvOffer = sleOffer->getJson(JsonOptions::Values::None);
5042
5043 STAmount saTakerGetsFunded;
5044 STAmount saOwnerFundsLimit = saOwnerFunds;
5045 Rate offerRate = parityRate;
5046
5047 if (rate != parityRate
5048 // Have a transfer fee.
5049 && uTakerID != book.out.account
5050 // Not taking offers of own IOUs.
5051 && book.out.account != uOfferOwnerID)
5052 // Offer owner not issuing ownfunds
5053 {
5054 // Need to charge a transfer fee to offer owner.
5055 offerRate = rate;
5056 saOwnerFundsLimit = divide(saOwnerFunds, offerRate);
5057 }
5058
5059 if (saOwnerFundsLimit >= saTakerGets)
5060 {
5061 // Sufficient funds no shenanigans.
5062 saTakerGetsFunded = saTakerGets;
5063 }
5064 else
5065 {
5066 // Only provide, if not fully funded.
5067 saTakerGetsFunded = saOwnerFundsLimit;
5068
5069 saTakerGetsFunded.setJson(jvOffer[jss::taker_gets_funded]);
5070
5071 // TODO(tom): The result of this expression is not used - what's
5072 // going on here?
5073 std::min(saTakerPays, multiply(saTakerGetsFunded, saDirRate, saTakerPays.asset()))
5074 .setJson(jvOffer[jss::taker_pays_funded]);
5075 }
5076
5077 STAmount saOwnerPays = (parityRate == offerRate)
5078 ? saTakerGetsFunded
5079 : std::min(saOwnerFunds, multiply(saTakerGetsFunded, offerRate));
5080
5081 umBalance[uOfferOwnerID] = saOwnerFunds - saOwnerPays;
5082
5083 if (!saOwnerFunds.isZero() || uOfferOwnerID == uTakerID)
5084 {
5085 // Only provide funded offers and offers of the taker.
5086 json::Value& jvOf = jvOffers.append(jvOffer);
5087 jvOf[jss::quality] = saDirRate.getText();
5088 }
5089 }
5090 }
5091
5092 // jvResult[jss::marker] = json::Value(json::ValueType::Array);
5093 // jvResult[jss::nodes] = json::Value(json::ValueType::Array);
5094}
5095
5096#endif
5097
5098inline void
5100{
5101 auto [counters, mode, start, initialSync] = accounting_.getCounterData();
5104 counters[static_cast<std::size_t>(mode)].dur += current;
5105
5106 std::scoped_lock const lock(statsMutex_);
5107 stats_.disconnectedDuration.set(
5108 counters[static_cast<std::size_t>(OperatingMode::DISCONNECTED)].dur.count());
5109 stats_.connectedDuration.set(
5110 counters[static_cast<std::size_t>(OperatingMode::CONNECTED)].dur.count());
5111 stats_.syncingDuration.set(
5112 counters[static_cast<std::size_t>(OperatingMode::SYNCING)].dur.count());
5113 stats_.trackingDuration.set(
5114 counters[static_cast<std::size_t>(OperatingMode::TRACKING)].dur.count());
5115 stats_.fullDuration.set(counters[static_cast<std::size_t>(OperatingMode::FULL)].dur.count());
5116
5117 stats_.disconnectedTransitions.set(
5118 counters[static_cast<std::size_t>(OperatingMode::DISCONNECTED)].transitions);
5119 stats_.connectedTransitions.set(
5120 counters[static_cast<std::size_t>(OperatingMode::CONNECTED)].transitions);
5121 stats_.syncingTransitions.set(
5122 counters[static_cast<std::size_t>(OperatingMode::SYNCING)].transitions);
5123 stats_.trackingTransitions.set(
5124 counters[static_cast<std::size_t>(OperatingMode::TRACKING)].transitions);
5125 stats_.fullTransitions.set(counters[static_cast<std::size_t>(OperatingMode::FULL)].transitions);
5126}
5127
5128void
5130{
5131 auto now = std::chrono::steady_clock::now();
5132
5133 std::scoped_lock const lock(mutex_);
5134 ++counters_[static_cast<std::size_t>(om)].transitions;
5135 if (om == OperatingMode::FULL && counters_[static_cast<std::size_t>(om)].transitions == 1)
5136 {
5139 }
5140 counters_[static_cast<std::size_t>(mode_)].dur +=
5142
5143 mode_ = om;
5144 start_ = now;
5145}
5146
5147void
5149{
5150 auto [counters, mode, start, initialSync] = getCounterData();
5153 counters[static_cast<std::size_t>(mode)].dur += current;
5154
5155 obj[jss::state_accounting] = json::ValueType::Object;
5156 for (auto i = static_cast<std::size_t>(OperatingMode::DISCONNECTED);
5157 i <= static_cast<std::size_t>(OperatingMode::FULL);
5158 ++i)
5159 {
5160 obj[jss::state_accounting][kStates[i]] = json::ValueType::Object;
5161 auto& state = obj[jss::state_accounting][kStates[i]];
5162 state[jss::transitions] = std::to_string(counters[i].transitions);
5163 state[jss::duration_us] = std::to_string(counters[i].dur.count());
5164 }
5165 obj[jss::server_state_duration_us] = std::to_string(current.count());
5166 if (initialSync != 0u)
5167 obj[jss::initial_sync_duration_us] = std::to_string(initialSync);
5168}
5169
5170//------------------------------------------------------------------------------
5171
5174 ServiceRegistry& registry,
5176 bool standalone,
5177 std::size_t minPeerCount,
5178 bool startValid,
5179 JobQueue& jobQueue,
5180 LedgerMaster& ledgerMaster,
5181 ValidatorKeys const& validatorKeys,
5182 boost::asio::io_context& ioCtx,
5184 beast::insight::Collector::ptr const& collector)
5185{
5187 registry,
5188 clock,
5189 standalone,
5190 minPeerCount,
5191 startValid,
5192 jobQueue,
5193 ledgerMaster,
5194 validatorKeys,
5195 ioCtx,
5196 journal,
5197 collector);
5198}
5199
5200} // namespace xrpl
T any_of(T... args)
T back_inserter(T... args)
T begin(T... args)
A generic endpoint for log messages.
Definition Journal.h:44
std::shared_ptr< Collector > ptr
Definition Collector.h:29
A metric for measuring an integral value.
Definition Gauge.h:21
A reference to a handler for performing polled collection.
Definition Hook.h:14
Decorator for streaming out compact json.
Lightweight wrapper to tag static string.
Definition json_value.h:48
Represents a JSON value.
Definition json_value.h:117
Value get(UInt index, Value const &defaultValue) const
If the array contains at least index+1 elements, returns the element value, otherwise returns default...
json::UInt UInt
Definition json_value.h:124
Value & append(Value const &value)
Append value to array at the end.
UInt size() const
Number of values in array or object.
bool isMember(char const *key) const
Return true if the object has a member named key.
A transaction that is in a closed ledger.
TxMeta const & getMeta() const
boost::container::flat_set< AccountID > const & getAffected() const
std::shared_ptr< STTx const > const & getTxn() const
bool isZero() const
Definition base_uint.h:562
bool isNonZero() const
Definition base_uint.h:567
iterator begin()
Definition base_uint.h:128
static constexpr std::size_t size()
Definition base_uint.h:548
Specifies an order book.
Definition Book.h:28
Holds transactions which were deferred to the next pass of consensus.
The role of a ClosureCounter is to assist in shutdown by letting callers wait for the completion of c...
std::uint32_t getLoadFee() const
Definition ClusterNode.h:33
NetClock::time_point getReportTime() const
Definition ClusterNode.h:39
PublicKey const & identity() const
Definition ClusterNode.h:45
std::string const & name() const
Definition ClusterNode.h:27
std::shared_ptr< InfoSub > pointer
Definition InfoSub.h:91
std::shared_ptr< InfoSub > const & ref
Definition InfoSub.h:97
std::weak_ptr< InfoSub > wptr
Definition InfoSub.h:95
A pool of threads to perform work.
Definition JobQueue.h:60
Manages the current fee schedule.
Manages load sources.
Definition LoadManager.h:28
void heartbeat()
Reset the stall detection timer.
static constexpr int kHoldLedgers
Definition LocalTxs.h:23
State accounting records two attributes for each possible server state: 1) Amount of time spent in ea...
void json(json::Value &obj) const
Output state counters in JSON format.
std::chrono::steady_clock::time_point const processStart_
static std::array< json::StaticString const, 5 > const kStates
std::array< Counters, 5 > counters_
void mode(OperatingMode om)
Record state transition.
std::chrono::steady_clock::time_point start_
Transaction with input flags and results to be applied in batches.
std::shared_ptr< Transaction > const transaction
TransactionStatus(std::shared_ptr< Transaction > t, bool a, bool l, FailHard f)
SubBookMapType subBook_
Guarded by bookLock_.
std::string getHostId(bool forAdmin)
void reportConsensusStateChange(ConsensusPhase phase)
void addAccountHistoryJob(SubAccountHistoryInfoWeak subInfo)
void clearNeedNetworkLedger() override
SubInfoMapType subAccount_
hash_map< AccountID, SubMapType > SubInfoMapType
static constexpr std::size_t kAccountCleanupChunk
Maximum number of account entries erased per accountLock_ acquisition during disconnect-time cleanup.
std::size_t const minPeerCount_
std::vector< TransactionStatus > transactions_
std::set< uint256 > pendingValidations_
void pubAccountTransaction(std::shared_ptr< ReadView const > const &ledger, AcceptedLedgerTx const &transaction, bool last)
NetworkOPsImp(ServiceRegistry &registry, NetworkOPs::clock_type &clock, bool standalone, std::size_t minPeerCount, bool startValid, JobQueue &jobQueue, LedgerMaster &ledgerMaster, ValidatorKeys const &validatorKeys, boost::asio::io_context &ioCtx, beast::Journal journal, beast::insight::Collector::ptr const &collector)
ClosureCounter< void, boost::system::error_code const & > waitHandlerCounter_
std::condition_variable cond_
MultiApiJson transJson(std::shared_ptr< STTx const > const &transaction, TER result, bool validated, std::shared_ptr< ReadView const > const &ledger, std::optional< std::reference_wrapper< TxMeta const > > meta)
bool unsubManifests(std::uint64_t uListener) override
json::Value getOwnerInfo(std::shared_ptr< ReadView const > lpLedger, AccountID const &account) override
void unsubAccount(InfoSub::ref ispListener, hash_set< AccountID > const &vnaAccountIDs, bool rt) override
void cleanupAccountHistorySubscriptions(std::uint64_t seq, hash_set< AccountID > const &accounts)
Erase one connection's entries from subAccountHistory_ in accountLock_-bounded chunks.
hash_map< Book, SubMapType > SubBookMapType
Maps each order book to its current set of subscribers.
bool subManifests(InfoSub::ref ispListener) override
void stateAccounting(json::Value &obj) override
void pubLedger(std::shared_ptr< ReadView const > const &lpAccepted) override
void stop() override
DispatchState dispatchState_
hash_map< std::string, InfoSub::pointer > subRpcMapType
void cleanupAccountSubscriptions(std::uint64_t seq, hash_set< AccountID > const &accounts, SubInfoMapType &subMap)
Erase one connection's entries from the given account map (subAccount_ or subRTAccount_) in accountLo...
ErrorCodeI subAccountHistory(InfoSub::ref ispListener, AccountID const &account) override
subscribe an account's new transactions and retrieve the account's historical transactions
bool subLedger(InfoSub::ref ispListener, json::Value &jvResult) override
void subAccount(InfoSub::ref ispListener, hash_set< AccountID > const &vnaAccountIDs, bool rt) override
void transactionBatch()
Apply transactions in batches.
void setTimer(boost::asio::steady_timer &timer, std::chrono::milliseconds const &expiryTime, std::function< void()> onExpire, std::function< void()> onError)
bool unsubRTTransactions(std::uint64_t uListener) override
beast::Journal const & journal() const override
Journal used by InfoSub for diagnostics that occur after the owning subsystem (e.g.
json::Value getLedgerFetchInfo() override
bool processTrustedProposal(RCLCxPeerPos proposal) override
subRpcMapType rpcSubMap_
InfoSub::pointer findRpcSubLocked(std::string const &strUrl)
Look up an RPC subscription without taking streamLock_.
void kickoffAccountHistory(std::shared_ptr< AcceptedLedger const > const &alpAccepted)
On the first published ledger only, start the delayed account-history streaming for any subscriptions...
void subAccountHistoryStart(std::shared_ptr< ReadView const > const &ledger, SubAccountHistoryInfoWeak &subInfo)
void pubValidation(std::shared_ptr< STValidation > const &val) override
bool subBook(InfoSub::ref ispListener, Book const &) override
InfoSub::pointer addRpcSub(std::string const &strUrl, InfoSub::ref) override
json::Value getConsensusInfo() override
ConsensusPhase lastConsensusPhase_
beast::Journal journal_
bool subServer(InfoSub::ref ispListener, json::Value &jvResult, bool admin) override
void endConsensus(std::unique_ptr< std::stringstream > const &clog) override
void setMode(OperatingMode om) override
void setAmendmentBlocked() override
void pubConsensus(ConsensusPhase phase)
bool isNeedNetworkLedger() override
bool unsubBookInternal(std::uint64_t uListener, Book const &) override
Remove a book subscription during InfoSub teardown.
DispatchState
Synchronization states for transaction batches.
SubInfoMapType subRTAccount_
std::atomic< bool > needNetworkLedger_
boost::asio::steady_timer heartbeatTimer_
std::array< SubMapType, SubTypes::SLastEntry > streamMaps_
One weak_ptr subscriber map per stream type.
bool subConsensus(InfoSub::ref ispListener) override
std::reference_wrapper< ServiceRegistry > registry_
static std::array< char const *, 5 > const kStates
bool unsubLedger(std::uint64_t uListener) override
bool checkLastClosedLedger(Overlay::PeerSequence const &, uint256 &networkClosed)
void pubProposedAccountTransaction(std::shared_ptr< ReadView const > const &ledger, std::shared_ptr< STTx const > const &transaction, TER result)
void unsubAccountHistoryInternal(std::uint64_t seq, AccountID const &account, bool historyOnly) override
void pubValidatedTransaction(std::shared_ptr< ReadView const > const &ledger, AcceptedLedgerTx const &transaction, bool last)
void pubBookTransaction(AcceptedLedgerTx const &transaction, MultiApiJson const &jvObj)
Fan transaction notifications out to all book subscribers.
void switchLastClosedLedger(std::shared_ptr< Ledger const > const &newLCL)
std::optional< PublicKey > const validatorPK_
std::mutex streamLock_
Guards streamMaps_[] and rpcSubMap_.
std::atomic< bool > amendmentBlocked_
bool isFull() override
void clearAmendmentWarned() override
LedgerMaster & ledgerMaster_
void publishLedgerStreams(std::shared_ptr< ReadView const > const &lpAccepted, std::shared_ptr< AcceptedLedger const > const &alpAccepted)
Send the ledgerClosed and book-changes stream updates for a ledger.
std::atomic< OperatingMode > mode_
void updateLocalTx(ReadView const &view) override
void clearLedgerFetch() override
std::unique_ptr< LocalTxs > localTX_
void apply(std::unique_lock< std::mutex > &batchLock)
Attempt to apply transactions and post-process based on the results.
InfoSub::pointer findRpcSub(std::string const &strUrl) override
bool isAmendmentBlocked() override
std::string strOperatingMode(OperatingMode const mode, bool const admin) const override
void setStandAlone() override
void setNeedNetworkLedger() override
hash_map< std::uint64_t, InfoSub::wptr > SubMapType
std::size_t getBookSubscribersCount() override
Total number of (book, subscriber) entries currently tracked.
std::mutex bookLock_
Guards subBook_.
bool unsubServer(std::uint64_t uListener) override
bool unsubConsensus(std::uint64_t uListener) override
void pubManifest(Manifest const &) override
std::mutex accountLock_
Guards subAccount_, subRTAccount_, subAccountHistory_.
void consensusViewChange() override
boost::asio::steady_timer accountHistoryTxTimer_
bool recvValidation(std::shared_ptr< STValidation > const &val, std::string const &source) override
void setUNLBlocked() override
bool unsubValidations(std::uint64_t uListener) override
bool subPeerStatus(InfoSub::ref ispListener) override
void cleanupSubscriptionMap(std::uint64_t seq, hash_set< AccountID > const &accounts, OuterMap &outerMap, BeforeErase &&beforeErase)
Erase one connection's entries from a subscription map in accountLock_-bounded chunks.
void scheduleAccountCleanup(std::uint64_t seq, hash_set< AccountID > rtAccounts, hash_set< AccountID > normalAccounts, hash_set< AccountID > historyAccounts) override
Schedule the server-side teardown of a disconnecting connection's account subscriptions off the destr...
void doTransactionAsync(std::shared_ptr< Transaction > transaction, bool bUnlimited, FailHard failtype)
For transactions not submitted by a locally connected client, fire and forget.
OperatingMode getOperatingMode() const override
std::optional< PublicKey > const validatorMasterPK_
void doTransactionSyncBatch(std::unique_lock< std::mutex > &lock, std::function< bool(std::unique_lock< std::mutex > const &)> retryCallback)
bool tryRemoveRpcSub(std::string const &strUrl) override
bool beginConsensus(uint256 const &networkClosed, std::unique_ptr< std::stringstream > const &clog) override
hash_map< AccountID, hash_map< std::uint64_t, SubAccountHistoryInfoWeak > > SubAccountHistoryMapType
void doTransactionSync(std::shared_ptr< Transaction > transaction, bool bUnlimited, FailHard failType)
For transactions submitted directly by a client, apply batch of transactions and wait for this transa...
void submitTransaction(std::shared_ptr< STTx const > const &) override
void setAmendmentWarned() override
void pubPeerStatus(std::function< json::Value(void)> const &) override
StateAccounting accounting_
SubAccountHistoryMapType subAccountHistory_
bool subValidations(InfoSub::ref ispListener) override
void setAccountHistoryJobTimer(SubAccountHistoryInfoWeak subInfo)
bool subRTTransactions(InfoSub::ref ispListener) override
std::atomic< bool > unlBlocked_
bool unsubBookChanges(std::uint64_t uListener) override
void unsubAccountHistory(InfoSub::ref ispListener, AccountID const &account, bool historyOnly) override
unsubscribe an account's transactions
void setStateTimer() override
Called to initially start our timers.
std::size_t getLocalTxCount() override
bool preProcessTransaction(std::shared_ptr< Transaction > &transaction)
void processTransaction(std::shared_ptr< Transaction > &transaction, bool bUnlimited, bool bLocal, FailHard failType) override
Process transactions as they arrive from the network or which are submitted by clients.
RCLConsensus consensus_
bool unsubTransactions(std::uint64_t uListener) override
bool isAmendmentWarned() override
void getBookPage(std::shared_ptr< ReadView const > &lpLedger, Book const &, AccountID const &uTakerID, bool const bProof, unsigned int iLimit, json::Value const &jvMarker, json::Value &jvResult) override
bool subTransactions(InfoSub::ref ispListener) override
std::mutex validationsMutex_
std::uint32_t acceptLedger(std::optional< std::chrono::milliseconds > consensusDelay) override
Accepts the current transaction tree, return the new ledger's sequence.
void clearUNLBlocked() override
bool isUNLBlocked() override
void pubProposedTransaction(std::shared_ptr< ReadView const > const &ledger, std::shared_ptr< STTx const > const &transaction, TER result) override
std::atomic< bool > amendmentWarned_
boost::asio::steady_timer clusterTimer_
bool unsubPeerStatus(std::uint64_t uListener) override
bool unsubBook(InfoSub::ref ispListener, Book const &) override
Remove a book subscription for a live subscriber.
void reportFeeChange() override
ServerFeeSummary lastFeeSummary_
void processTransactionSet(CanonicalTXSet const &set) override
Process a set of transactions synchronously, and ensuring that they are processed in one batch.
void mapComplete(std::shared_ptr< SHAMap > const &map, bool fromAcquire) override
bool isBlocked() override
~NetworkOPsImp() override
json::Value getServerInfo(bool human, bool admin, bool counters) override
void unsubAccountInternal(std::uint64_t seq, hash_set< AccountID > const &vnaAccountIDs, bool rt) override
bool subBookChanges(InfoSub::ref ispListener) override
Provides server functionality for clients.
Definition NetworkOPs.h:82
beast::AbstractClock< std::chrono::steady_clock > clock_type
Definition NetworkOPs.h:84
Writable ledger view that accumulates state and tx changes.
Definition OpenView.h:59
std::vector< std::shared_ptr< Peer > > PeerSequence
Definition Overlay.h:66
Manages the generic consensus algorithm for use by the RCL.
A peer's signed, proposed position for use in RCLConsensus.
PublicKey const & publicKey() const
Public key of peer that sent the proposal.
Represents a set of transactions in RCLConsensus.
Definition RCLCxTx.h:60
Wraps a ledger instance for use in generic Validations LedgerTrie.
static std::string getWordFromBlob(void const *blob, size_t bytes)
Chooses a single dictionary word from the data.
Definition RFC1751.cpp:434
Collects logging information.
std::unique_ptr< std::stringstream > const & ss()
A view into a ledger.
Definition ReadView.h:41
virtual SLE::const_pointer read(Keylet const &k) const =0
Return the state item associated with a key.
virtual std::optional< key_type > succ(key_type const &key, std::optional< key_type > const &last=std::nullopt) const =0
Return the key of the next state item.
std::vector< AccountTx > AccountTxs
std::string getText() const override
Definition STAmount.cpp:646
Asset const & asset() const
Definition STAmount.h:496
void setJson(json::Value &) const
Definition STAmount.cpp:606
std::shared_ptr< STLedgerEntry > pointer
std::shared_ptr< STLedgerEntry const > const_pointer
Automatically unlocks and re-locks a unique_lock object.
Definition scope.h:197
std::size_t size() const noexcept
Definition Serializer.h:51
void const * data() const noexcept
Definition Serializer.h:57
Service registry for dependency injection.
static time_point now()
Validator keys and manifest as set in configuration file.
json::Value jsonClipped() const
Definition XRPAmount.h:210
constexpr double decimalXRP() const
Definition XRPAmount.h:257
T duration_cast(T... args)
T emplace_back(T... args)
T emplace(T... args)
T empty(T... args)
T end(T... args)
T erase(T... args)
T exchange(T... args)
T find(T... args)
T get(T... args)
T insert(T... args)
T is_sorted(T... args)
T lock(T... args)
T make_pair(T... args)
T make_shared(T... args)
T make_unique(T... args)
T max(T... args)
T min(T... args)
void rngfill(void *const buffer, std::size_t const bytes, Generator &g)
Definition rngfill.h:11
constexpr Zero kZero
Definition Zero.h:30
JSON (JavaScript Object Notation).
Definition json_errors.h:5
int Int
unsigned int UInt
@ Array
array value (ordered list)
Definition json_value.h:28
@ Object
object value (collection of name/value pairs).
Definition json_value.h:29
STL namespace.
std::string const & getVersionString()
Server version.
Definition BuildInfo.cpp:68
TER valid(STTx const &tx, ReadView const &view, AccountID const &src, beast::Journal j)
std::string const & getCommitHash()
Definition Git.cpp:18
std::string const & getBuildBranch()
Definition Git.cpp:25
Keylet offer(AccountID const &id, SeqProxy const &seq) noexcept
An offer from an account.
Definition Indexes.cpp:276
Keylet book(Book const &b)
The beginning of an order book.
Definition Indexes.cpp:247
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:373
Keylet child(uint256 const &key) noexcept
Any item that can be in an owner dir.
Definition Indexes.cpp:204
Keylet page(uint256 const &root, std::uint64_t const index=0) noexcept
A page in a directory.
Definition Indexes.cpp:379
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:198
void insertMPTokenIssuanceID(json::Value &response, std::shared_ptr< STTx const > const &transaction, TxMeta const &transactionMeta)
void insertDeliveredAmount(json::Value &meta, ReadView const &, std::shared_ptr< STTx const > const &serializedTx, TxMeta const &)
Add a delivered_amount field to the meta input/output parameter.
std::optional< std::string > encodeCTID(uint32_t ledgerSeq, uint32_t txnIndex, uint32_t networkID) noexcept
Encodes ledger sequence, transaction index, and network ID into a CTID string.
Definition CTID.h:36
static constexpr std::integral_constant< unsigned, Version > kApiVersion
Definition ApiVersion.h:39
void insertNFTSyntheticInJson(json::Value &, std::shared_ptr< STTx const > const &, TxMeta const &)
Adds common synthetic fields to transaction-related JSON responses.
json::Value computeBookChanges(std::shared_ptr< L const > const &lpAccepted)
Definition BookChanges.h:39
Rate rate(Env &env, Account const &account, std::uint32_t const &seq)
Definition escrow.cpp:60
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
@ WarnRpcAmendmentBlocked
Definition ErrorCodes.h:159
@ WarnRpcUnsupportedMajority
Definition ErrorCodes.h:158
@ WarnRpcExpiredValidatorList
Definition ErrorCodes.h:160
STAmount divide(STAmount const &amount, Rate const &rate)
Definition Rate2.cpp:69
@ terQUEUED
Definition TER.h:221
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,...
constexpr FlagValue tfInnerBatchTxn
Definition TxFlags.h:44
bool isTerRetry(TER x) noexcept
Definition TER.h:670
ErrorCodeI
Definition ErrorCodes.h:23
@ RpcSuccess
Definition ErrorCodes.h:27
@ RpcInvalidParams
Definition ErrorCodes.h:67
void handleNewValidation(Application &app, std::shared_ptr< STValidation > const &val, std::string const &source, BypassAccept const bypassAccept, std::optional< beast::Journal > j)
Handle a new validation.
@ WrongLedger
We have the wrong ledger and are attempting to acquire it.
@ Proposing
We are normal participant in consensus and propose our position.
std::optional< std::uint64_t > mulDiv(std::uint64_t value, std::uint64_t mul, std::uint64_t div)
Return value*mul/div accurately.
SendIfPred< Predicate > sendIf(std::shared_ptr< Message > const &m, Predicate const &f)
Helper function to aid in type deduction.
Definition predicates.h:61
void forAllApiVersions(Fn const &fn, Args &&... args)
Definition ApiVersion.h:159
@ SigBad
Signature is bad.
Definition apply.h:28
@ Valid
Signature and local checks are good / passed.
Definition apply.h:36
T get(Section const &section, std::string const &name, T const &defaultValue=T{})
Retrieve a key/value pair from a section.
constexpr std::size_t kMaxPoppedTransactions
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:13
std::unique_ptr< FeeVote > makeFeeVote(FeeSetup const &setup, beast::Journal journal)
Create an instance of the FeeVote logic.
std::pair< Validity, std::string > checkValidity(HashRouter &router, STTx const &tx, Rules const &rules)
Checks transaction signature and local checks.
Definition apply.cpp:36
Rules makeRulesGivenLedger(DigestAwareReadView const &ledger, Rules const &current)
Definition ReadView.cpp:61
FeeSetup setupFeeVote(Section const &section)
@ tefPAST_SEQ
Definition TER.h:167
std::uint64_t getQuality(uint256 const &uBase)
Definition Indexes.cpp:172
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:95
std::unordered_set< Value, Hash, Pred, Allocator > hash_set
Number root(Number f, unsigned d)
CsprngEngine & cryptoPrng()
The default cryptographically secure PRNG.
bool transResultInfo(TER code, std::string &token, std::string &text)
Definition TER.cpp:236
Seed generateSeed(std::string const &passPhrase)
Generate a seed deterministically.
Definition Seed.cpp:58
constexpr Dest safeCast(Src s) noexcept
Definition safe_cast.h:21
std::pair< PublicKey, SecretKey > generateKeyPair(KeyType type, Seed const &seed)
Generate a key pair deterministically.
constexpr std::uint32_t kFeeUnitsDeprecated
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:651
std::string toStringIso(date::sys_time< Duration > tp)
Definition chrono.h:70
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
bool isGlobalFrozen(ReadView const &view, AccountID const &issuer)
Check if the issuer has the global freeze flag set.
STAmount amountFromQuality(std::uint64_t rate)
Definition STAmount.cpp:895
@ JtClientAcctHist
Definition Job.h:35
@ JtTransaction
Definition Job.h:48
@ JtBatch
Definition Job.h:51
@ JtTxnProc
Definition Job.h:68
@ JtClientConsensus
Definition Job.h:34
@ JtNetopCluster
Definition Job.h:61
@ JtClientFeeChange
Definition Job.h:33
bool isTefFailure(TER x) noexcept
Definition TER.h:664
std::unique_ptr< LocalTxs > makeLocalTxs()
Definition LocalTxs.cpp:185
hash_set< Book > affectedBooks(AcceptedLedgerTx const &alTx, beast::Journal const &j)
Extract the set of books affected by a transaction.
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
std::unique_ptr< NetworkOPs > makeNetworkOPs(ServiceRegistry &registry, NetworkOPs::clock_type &clock, bool standalone, std::size_t minPeerCount, bool startValid, JobQueue &jobQueue, LedgerMaster &ledgerMaster, ValidatorKeys const &validatorKeys, boost::asio::io_context &ioSvc, beast::Journal journal, beast::insight::Collector::ptr const &collector)
uint256 getQualityNext(uint256 const &uBase)
Definition Indexes.cpp:164
ConsensusPhase
Phases of consensus for a single ledger round.
@ Open
We haven't closed our ledger yet, but others might have.
Rate const kParityRate
A transfer rate signifying a 1:1 exchange.
json::Value getJson(LedgerFill const &fill)
Return a new json::Value representing the ledger with given options.
AccountID calcAccountID(PublicKey const &pk)
uint256 getBookBase(Book const &book)
Definition Indexes.cpp:122
static std::array< char const *, 5 > const kStateNames
constexpr auto kMuldivMax
Definition mulDiv.h:8
std::unordered_map< Key, Value, Hash, Pred, Allocator > hash_map
ApplyFlags
Definition ApplyView.h:27
@ TapUnlimited
Definition ApplyView.h:39
@ TapFailHard
Definition ApplyView.h:32
@ TapNone
Definition ApplyView.h:28
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:34
detail::MultiApiJson< rpc::kApiMinimumSupportedVersion, rpc::kApiMaximumValidVersion > MultiApiJson
bool isTelLocal(TER x) noexcept
Definition TER.h:652
bool cdirNext(ReadView const &view, uint256 const &root, SLE::const_pointer &page, unsigned int &index, uint256 &entry)
Returns the next entry in the directory, advancing the index.
@ temINVALID_FLAG
Definition TER.h:99
@ temBAD_SIGNATURE
Definition TER.h:93
bool isTesSuccess(TER x) noexcept
Definition TER.h:676
static std::uint32_t trunc32(std::uint64_t v)
TERSubset< CanCvtToTER > TER
Definition TER.h:647
STAmount multiply(STAmount const &amount, Number const &frac, Number::RoundingMode rm)
bool cdirFirst(ReadView const &view, uint256 const &root, SLE::const_pointer &page, unsigned int &index, uint256 &entry)
Returns the first entry in the directory, advancing the index.
OperatingMode
Specifies the mode under which the server believes it's operating.
Definition NetworkOPs.h:60
@ TRACKING
convinced we agree with the network
Definition NetworkOPs.h:64
@ DISCONNECTED
not ready to process requests
Definition NetworkOPs.h:61
@ CONNECTED
convinced we are talking to the network
Definition NetworkOPs.h:62
@ FULL
we have the ledger and can even validate
Definition NetworkOPs.h:65
@ SYNCING
fallen slightly behind
Definition NetworkOPs.h:63
std::shared_ptr< STTx const > sterilize(STTx const &stx)
Sterilize a transaction.
Definition STTx.cpp:877
static auto const kGenesisAccountId
BaseUInt< 256 > uint256
Definition base_uint.h:580
bool isTemMalformed(TER x) noexcept
Definition TER.h:658
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j, SpendableHandling includeFullBalance=SpendableHandling::SimpleBalance)
@ tesSUCCESS
Definition TER.h:245
XRPL_NO_SANITIZE_ADDRESS void Throw(Args &&... args)
Definition contract.h:52
CanonicalTXSet OrderedTxs
Definition OpenLedger.h:35
T owns_lock(T... args)
T push_back(T... args)
T ref(T... args)
T reserve(T... args)
T reset(T... args)
T set_intersection(T... args)
T size(T... args)
T str(T... args)
static constexpr auto kPort
Definition Constants.h:145
static constexpr auto kIp
Definition Constants.h:114
PublicKey masterKey
The master key associated with this manifest.
Definition Manifest.h:84
std::string serialized
The manifest in serialized form.
Definition Manifest.h:79
Blob getMasterSignature() const
Returns manifest master key signature.
std::string domain
The domain, if one was specified in the manifest; empty otherwise.
Definition Manifest.h:102
std::optional< Blob > getSignature() const
Returns manifest signature.
std::optional< PublicKey > signingKey
The ephemeral key associated with this manifest.
Definition Manifest.h:92
std::uint32_t sequence
The sequence number of this manifest.
Definition Manifest.h:97
Server fees published on server subscription.
std::optional< TxQ::Metrics > em
bool operator!=(ServerFeeSummary const &b) const
bool operator==(ServerFeeSummary const &b) const
beast::insight::Gauge fullTransitions
beast::insight::Gauge disconnectedTransitions
beast::insight::Gauge connectedDuration
Stats(Handler const &handler, beast::insight::Collector::ptr const &collector)
beast::insight::Gauge trackingTransitions
beast::insight::Gauge fullDuration
beast::insight::Gauge syncingDuration
beast::insight::Gauge disconnectedDuration
beast::insight::Gauge connectedTransitions
beast::insight::Gauge trackingDuration
beast::insight::Hook hook
beast::insight::Gauge syncingTransitions
SubAccountHistoryIndex(AccountID const &accountId)
std::shared_ptr< SubAccountHistoryIndex > index
std::shared_ptr< SubAccountHistoryIndex > index
Select all peers (except optional excluded) that are in our cluster.
Definition predicates.h:127
Represents a transfer rate.
Definition Rate.h:21
static constexpr auto kPortGrpc
Definition Constants.h:45
Sends a message to all peers.
Definition predicates.h:15
Changes in trusted nodes after updating validator list.
hash_set< NodeID > added
hash_set< NodeID > removed
Structure returned by TxQ::getMetrics, expressed in reference fee level units.
Definition TxQ.h:185
void set(char const *key, auto const &v)
IsMemberResult isMember(char const *key) const
Data format for exchanging consumption information across peers.
Definition Gossip.h:13
std::vector< Item > items
Definition Gossip.h:27
T time_since_epoch(T... args)
T to_string(T... args)
T unlock(T... args)
T value_or(T... args)
T what(T... args)