rippled
Loading...
Searching...
No Matches
Application.cpp
1#include <xrpld/app/consensus/RCLValidations.h>
2#include <xrpld/app/ledger/InboundLedgers.h>
3#include <xrpld/app/ledger/InboundTransactions.h>
4#include <xrpld/app/ledger/LedgerCleaner.h>
5#include <xrpld/app/ledger/LedgerMaster.h>
6#include <xrpld/app/ledger/LedgerReplayer.h>
7#include <xrpld/app/ledger/LedgerToJson.h>
8#include <xrpld/app/ledger/OpenLedger.h>
9#include <xrpld/app/ledger/OrderBookDB.h>
10#include <xrpld/app/ledger/PendingSaves.h>
11#include <xrpld/app/ledger/TransactionMaster.h>
12#include <xrpld/app/main/Application.h>
13#include <xrpld/app/main/BasicApp.h>
14#include <xrpld/app/main/GRPCServer.h>
15#include <xrpld/app/main/LoadManager.h>
16#include <xrpld/app/main/NodeIdentity.h>
17#include <xrpld/app/main/NodeStoreScheduler.h>
18#include <xrpld/app/misc/AmendmentTable.h>
19#include <xrpld/app/misc/HashRouter.h>
20#include <xrpld/app/misc/LoadFeeTrack.h>
21#include <xrpld/app/misc/NetworkOPs.h>
22#include <xrpld/app/misc/SHAMapStore.h>
23#include <xrpld/app/misc/TxQ.h>
24#include <xrpld/app/misc/ValidatorKeys.h>
25#include <xrpld/app/misc/ValidatorSite.h>
26#include <xrpld/app/paths/PathRequests.h>
27#include <xrpld/app/rdb/RelationalDatabase.h>
28#include <xrpld/app/tx/apply.h>
29#include <xrpld/overlay/Cluster.h>
30#include <xrpld/overlay/PeerSet.h>
31#include <xrpld/overlay/make_Overlay.h>
32#include <xrpld/shamap/NodeFamily.h>
33
34#include <xrpl/basics/ByteUtilities.h>
35#include <xrpl/basics/ResolverAsio.h>
36#include <xrpl/basics/random.h>
37#include <xrpl/beast/asio/io_latency_probe.h>
38#include <xrpl/beast/core/LexicalCast.h>
39#include <xrpl/core/PeerReservationTable.h>
40#include <xrpl/core/PerfLog.h>
41#include <xrpl/crypto/csprng.h>
42#include <xrpl/json/json_reader.h>
43#include <xrpl/nodestore/DummyScheduler.h>
44#include <xrpl/protocol/ApiVersion.h>
45#include <xrpl/protocol/BuildInfo.h>
46#include <xrpl/protocol/Feature.h>
47#include <xrpl/protocol/Protocol.h>
48#include <xrpl/protocol/STParsedJSON.h>
49#include <xrpl/rdb/DatabaseCon.h>
50#include <xrpl/resource/Fees.h>
51#include <xrpl/server/Wallet.h>
52
53#include <boost/algorithm/string/predicate.hpp>
54#include <boost/asio/steady_timer.hpp>
55#include <boost/system/error_code.hpp>
56
57#include <date/date.h>
58
59#include <chrono>
60#include <condition_variable>
61#include <cstring>
62#include <fstream>
63#include <limits>
64#include <mutex>
65#include <optional>
66#include <utility>
67
68namespace xrpl {
69
70static void
71fixConfigPorts(Config& config, Endpoints const& endpoints);
72
73// VFALCO TODO Move the function definitions into the class declaration
74class ApplicationImp : public Application, public BasicApp
75{
76private:
78 {
79 private:
84
85 public:
90 boost::asio::io_context& ios)
91 : m_event(ev), m_journal(journal), m_probe(interval, ios), lastSample_{}
92 {
93 }
94
95 void
97 {
98 m_probe.sample(std::ref(*this));
99 }
100
101 template <class Duration>
102 void
103 operator()(Duration const& elapsed)
104 {
105 using namespace std::chrono;
106 auto const lastSample = ceil<milliseconds>(elapsed);
107
108 lastSample_ = lastSample;
109
110 if (lastSample >= 10ms)
111 m_event.notify(lastSample);
112 if (lastSample >= 500ms)
113 {
114 JLOG(m_journal.warn()) << "io_context latency = " << lastSample.count();
115 }
116 }
117
119 get() const
120 {
121 return lastSample_.load();
122 }
123
124 void
126 {
127 m_probe.cancel();
128 }
129
130 void
132 {
134 }
135 };
136
137public:
141
143
147
148 // Required by the SHAMapStore
150
157
162
164
167 // VFALCO TODO Make OrderBookDB abstract
191 boost::asio::steady_timer sweepTimer_;
192 boost::asio::steady_timer entropyTimer_;
193
198
199 boost::asio::signal_set m_signals;
200
202
204
206
208
210
211 //--------------------------------------------------------------------------
212
213 static std::size_t
215 {
216#if XRPL_SINGLE_IO_SERVICE_THREAD
217 return 1;
218#else
219
220 if (config.IO_WORKERS > 0)
221 return config.IO_WORKERS;
222
223 auto const cores = std::thread::hardware_concurrency();
224
225 // Use a single thread when running on under-provisioned systems
226 // or if we are configured to use minimal resources.
227 if ((cores == 1) || ((config.NODE_SIZE == 0) && (cores == 2)))
228 return 1;
229
230 // Otherwise, prefer six threads.
231 return 6;
232#endif
233 }
234
235 //--------------------------------------------------------------------------
236
239 , config_(std::move(config))
240 , logs_(std::move(logs))
241 , timeKeeper_(std::move(timeKeeper))
243 , m_journal(logs_->journal("Application"))
244
245 // PerfLog must be started before any other threads are launched.
246 , perfLog_(perf::make_PerfLog(
247 perf::setup_PerfLog(config_->section("perf"), config_->CONFIG_DIR),
248 *this,
249 logs_->journal("PerfLog"),
250 [this] { signalStop("PerfLog"); }))
251
252 , m_txMaster(*this)
253
254 , m_collectorManager(make_CollectorManager(config_->section(SECTION_INSIGHT), logs_->journal("Collector")))
255
259 return 1;
260
261 if (config->WORKERS)
262 return config->WORKERS;
263
264 auto count = static_cast<int>(std::thread::hardware_concurrency());
265
266 // Be more aggressive about the number of threads to use
267 // for the job queue if the server is configured as
268 // "large" or "huge" if there are enough cores.
269 if (config->NODE_SIZE >= 4 && count >= 16)
270 count = 6 + std::min(count, 8);
271 else if (config->NODE_SIZE >= 3 && count >= 8)
272 count = 4 + std::min(count, 6);
273 else
274 count = 2 + std::min(count, 4);
275
276 return count;
277 }(config_),
278 m_collectorManager->group("jobq"),
279 logs_->journal("JobQueue"),
280 *logs_,
281 *perfLog_))
282
284
285 , m_shaMapStore(make_SHAMapStore(*this, m_nodeStoreScheduler, logs_->journal("SHAMapStore")))
286
287 , m_tempNodeCache("NodeCache", 16384, std::chrono::seconds{90}, stopwatch(), logs_->journal("TaggedCache"))
288
289 , cachedSLEs_("Cached SLEs", 0, std::chrono::minutes(1), stopwatch(), logs_->journal("CachedSLEs"))
290
292
293 , m_resourceManager(Resource::make_Manager(m_collectorManager->collector(), logs_->journal("Resource")))
294
295 , m_nodeStore(m_shaMapStore->makeNodeStore(config_->PREFETCH_WORKERS > 0 ? config_->PREFETCH_WORKERS : 4))
296
298
299 , m_orderBookDB(*this)
300
302 std::make_unique<PathRequests>(*this, logs_->journal("PathRequest"), m_collectorManager->collector()))
303
304 , m_ledgerMaster(std::make_unique<LedgerMaster>(
305 *this,
306 stopwatch(),
307 m_collectorManager->collector(),
308 logs_->journal("LedgerMaster")))
309
310 , ledgerCleaner_(make_LedgerCleaner(*this, logs_->journal("LedgerCleaner")))
311
312 // VFALCO NOTE must come before NetworkOPs to prevent a crash due
313 // to dependencies in the destructor.
314 //
316
318 *this,
319 m_collectorManager->collector(),
320 [this](std::shared_ptr<SHAMap> const& set, bool fromAcquire) { gotTXSet(set, fromAcquire); }))
321
323
325 "AcceptedLedger",
326 4,
328 stopwatch(),
329 logs_->journal("TaggedCache"))
330
332 *this,
333 stopwatch(),
334 config_->standalone(),
335 config_->NETWORK_QUORUM,
336 config_->START_VALID,
337 *m_jobQueue,
341 logs_->journal("NetworkOPs"),
342 m_collectorManager->collector()))
343
344 , cluster_(std::make_unique<Cluster>(logs_->journal("Overlay")))
345
346 , peerReservations_(std::make_unique<PeerReservationTable>(logs_->journal("PeerReservationTable")))
347
348 , validatorManifests_(std::make_unique<ManifestCache>(logs_->journal("ManifestCache")))
349
350 , publisherManifests_(std::make_unique<ManifestCache>(logs_->journal("ManifestCache")))
351
356 config_->legacy("database_path"),
357 logs_->journal("ValidatorList"),
358 config_->VALIDATION_QUORUM))
359
361
363 *this,
365 *m_jobQueue,
369
370 , mFeeTrack(std::make_unique<LoadFeeTrack>(logs_->journal("LoadManager")))
371
373
374 , mValidations(ValidationParms(), stopwatch(), *this, logs_->journal("Validations"))
375
376 , m_loadManager(make_LoadManager(*this, logs_->journal("LoadManager")))
377
378 , txQ_(std::make_unique<TxQ>(setup_TxQ(*config_), logs_->journal("TxQ")))
379
381
383
385
386 , checkSigs_(true)
387
388 , m_resolver(ResolverAsio::New(get_io_context(), logs_->journal("Resolver")))
389
391 m_collectorManager->collector()->make_event("ios_latency"),
392 logs_->journal("Application"),
396 {
398
399 add(m_resourceManager.get());
400
401 //
402 // VFALCO - READ THIS!
403 //
404 // Do not start threads, open sockets, or do any sort of "real work"
405 // inside the constructor. Put it in start instead. Or if you must,
406 // put it in setup (but everything in setup should be moved to start
407 // anyway.
408 //
409 // The reason is that the unit tests require an Application object to
410 // be created. But we don't actually start all the threads, sockets,
411 // and services when running the unit tests. Therefore anything which
412 // needs to be stopped will not get stopped correctly if it is
413 // started in this constructor.
414 //
415
416 add(ledgerCleaner_.get());
417 }
418
419 //--------------------------------------------------------------------------
420
421 bool
422 setup(boost::program_options::variables_map const& cmdline) override;
423 void
424 start(bool withTimers) override;
425 void
426 run() override;
427 void
428 signalStop(std::string msg) override;
429 bool
430 checkSigs() const override;
431 void
432 checkSigs(bool) override;
433 bool
434 isStopping() const override;
435 int
436 fdRequired() const override;
437
438 //--------------------------------------------------------------------------
439
441 instanceID() const override
442 {
443 return instanceCookie_;
444 }
445
446 Logs&
447 logs() override
448 {
449 return *logs_;
450 }
451
452 Config&
453 config() override
454 {
455 return *config_;
456 }
457
460 {
461 return *m_collectorManager;
462 }
463
464 Family&
465 getNodeFamily() override
466 {
467 return nodeFamily_;
468 }
469
471 timeKeeper() override
472 {
473 return *timeKeeper_;
474 }
475
476 JobQueue&
477 getJobQueue() override
478 {
479 return *m_jobQueue;
480 }
481
483 nodeIdentity() override
484 {
485 if (nodeIdentity_)
486 return *nodeIdentity_;
487
488 LogicError("Accessing Application::nodeIdentity() before it is initialized.");
489 }
490
492 getValidationPublicKey() const override
493 {
494 if (!validatorKeys_.keys)
495 return {};
496
497 return validatorKeys_.keys->publicKey;
498 }
499
501 getOPs() override
502 {
503 return *m_networkOPs;
504 }
505
506 virtual ServerHandler&
508 {
509 XRPL_ASSERT(
511 "xrpl::ApplicationImp::getServerHandler : non-null server "
512 "handle");
513 return *serverHandler_;
514 }
515
516 boost::asio::io_context&
517 getIOContext() override
518 {
519 return get_io_context();
520 }
521
523 getIOLatency() override
524 {
525 return m_io_latency_sampler.get();
526 }
527
530 {
531 return *m_ledgerMaster;
532 }
533
536 {
537 return *ledgerCleaner_;
538 }
539
542 {
543 return *m_ledgerReplayer;
544 }
545
548 {
549 return *m_inboundLedgers;
550 }
551
554 {
555 return *m_inboundTransactions;
556 }
557
560 {
562 }
563
564 void
565 gotTXSet(std::shared_ptr<SHAMap> const& set, bool fromAcquire)
566 {
567 if (set)
568 m_networkOPs->mapComplete(set, fromAcquire);
569 }
570
573 {
574 return m_txMaster;
575 }
576
578 getPerfLog() override
579 {
580 return *perfLog_;
581 }
582
583 NodeCache&
585 {
586 return m_tempNodeCache;
587 }
588
590 getNodeStore() override
591 {
592 return *m_nodeStore;
593 }
594
596 getMasterMutex() override
597 {
598 return m_masterMutex;
599 }
600
602 getLoadManager() override
603 {
604 return *m_loadManager;
605 }
606
609 {
610 return *m_resourceManager;
611 }
612
614 getOrderBookDB() override
615 {
616 return m_orderBookDB;
617 }
618
621 {
622 return *m_pathRequests;
623 }
624
626 cachedSLEs() override
627 {
628 return cachedSLEs_;
629 }
630
633 {
634 return *m_amendmentTable;
635 }
636
638 getFeeTrack() override
639 {
640 return *mFeeTrack;
641 }
642
644 getHashRouter() override
645 {
646 return *hashRouter_;
647 }
648
650 getValidations() override
651 {
652 return mValidations;
653 }
654
656 validators() override
657 {
658 return *validators_;
659 }
660
662 validatorSites() override
663 {
664 return *validatorSites_;
665 }
666
669 {
670 return *validatorManifests_;
671 }
672
675 {
676 return *publisherManifests_;
677 }
678
679 Cluster&
680 cluster() override
681 {
682 return *cluster_;
683 }
684
687 {
688 return *peerReservations_;
689 }
690
692 getSHAMapStore() override
693 {
694 return *m_shaMapStore;
695 }
696
698 pendingSaves() override
699 {
700 return pendingSaves_;
701 }
702
704 openLedger() override
705 {
706 return *openLedger_;
707 }
708
709 OpenLedger const&
710 openLedger() const override
711 {
712 return *openLedger_;
713 }
714
715 Overlay&
716 overlay() override
717 {
718 XRPL_ASSERT(overlay_, "xrpl::ApplicationImp::overlay : non-null overlay");
719 return *overlay_;
720 }
721
722 TxQ&
723 getTxQ() override
724 {
725 XRPL_ASSERT(txQ_, "xrpl::ApplicationImp::getTxQ : non-null transaction queue");
726 return *txQ_;
727 }
728
731 {
732 XRPL_ASSERT(
734 "xrpl::ApplicationImp::getRelationalDatabase : non-null "
735 "relational database");
736 return *mRelationalDatabase;
737 }
738
740 getWalletDB() override
741 {
742 XRPL_ASSERT(mWalletDB, "xrpl::ApplicationImp::getWalletDB : non-null wallet database");
743 return *mWalletDB;
744 }
745
746 bool
747 serverOkay(std::string& reason) override;
748
750 journal(std::string const& name) override;
751
752 //--------------------------------------------------------------------------
753
754 bool
756 {
757 XRPL_ASSERT(
758 mWalletDB.get() == nullptr,
759 "xrpl::ApplicationImp::initRelationalDatabase : null wallet "
760 "database");
761
762 try
763 {
765
766 // wallet database
768 setup.useGlobalPragma = false;
769
771 }
772 catch (std::exception const& e)
773 {
774 JLOG(m_journal.fatal()) << "Failed to initialize SQL databases: " << e.what();
775 return false;
776 }
777
778 return true;
779 }
780
781 bool
783 {
784 if (config_->doImport)
785 {
786 auto j = logs_->journal("NodeObject");
787 NodeStore::DummyScheduler dummyScheduler;
790 dummyScheduler,
791 0,
793 j);
794
795 JLOG(j.warn()) << "Starting node import from '" << source->getName() << "' to '" << m_nodeStore->getName()
796 << "'.";
797
798 using namespace std::chrono;
799 auto const start = steady_clock::now();
800
801 m_nodeStore->importDatabase(*source);
802
803 auto const elapsed = duration_cast<seconds>(steady_clock::now() - start);
804 JLOG(j.warn()) << "Node import from '" << source->getName() << "' took " << elapsed.count() << " seconds.";
805 }
806
807 return true;
808 }
809
810 //--------------------------------------------------------------------------
811 //
812 // PropertyStream
813 //
814
815 void
817 {
818 }
819
820 //--------------------------------------------------------------------------
821
822 void
824 {
825 // Only start the timer if waitHandlerCounter_ is not yet joined.
826 if (auto optionalCountedHandler = waitHandlerCounter_.wrap([this](boost::system::error_code const& e) {
827 if (e.value() == boost::system::errc::success)
828 {
829 m_jobQueue->addJob(jtSWEEP, "sweep", [this]() { doSweep(); });
830 }
831 // Recover as best we can if an unexpected error occurs.
832 if (e.value() != boost::system::errc::success && e.value() != boost::asio::error::operation_aborted)
833 {
834 // Try again later and hope for the best.
835 JLOG(m_journal.error()) << "Sweep timer got error '" << e.message() << "'. Restarting timer.";
836 setSweepTimer();
837 }
838 }))
839 {
840 using namespace std::chrono;
841 sweepTimer_.expires_after(
842 seconds{config_->SWEEP_INTERVAL.value_or(config_->getValueFor(SizedItem::sweepInterval))});
843 sweepTimer_.async_wait(std::move(*optionalCountedHandler));
844 }
845 }
846
847 void
849 {
850 // Only start the timer if waitHandlerCounter_ is not yet joined.
851 if (auto optionalCountedHandler = waitHandlerCounter_.wrap([this](boost::system::error_code const& e) {
852 if (e.value() == boost::system::errc::success)
853 {
854 crypto_prng().mix_entropy();
855 setEntropyTimer();
856 }
857 // Recover as best we can if an unexpected error occurs.
858 if (e.value() != boost::system::errc::success && e.value() != boost::asio::error::operation_aborted)
859 {
860 // Try again later and hope for the best.
861 JLOG(m_journal.error()) << "Entropy timer got error '" << e.message() << "'. Restarting timer.";
862 setEntropyTimer();
863 }
864 }))
865 {
866 using namespace std::chrono_literals;
867 entropyTimer_.expires_after(5min);
868 entropyTimer_.async_wait(std::move(*optionalCountedHandler));
869 }
870 }
871
872 void
874 {
875 if (!config_->standalone() && !getRelationalDatabase().transactionDbHasSpace(*config_))
876 {
877 signalStop("Out of transaction DB space");
878 }
879
880 // VFALCO NOTE Does the order of calls matter?
881 // VFALCO TODO fix the dependency inversion using an observer,
882 // have listeners register for "onSweep ()" notification.
883
884 {
885 std::shared_ptr<FullBelowCache const> const fullBelowCache = nodeFamily_.getFullBelowCache();
886
887 std::shared_ptr<TreeNodeCache const> const treeNodeCache = nodeFamily_.getTreeNodeCache();
888
889 std::size_t const oldFullBelowSize = fullBelowCache->size();
890 std::size_t const oldTreeNodeSize = treeNodeCache->size();
891
892 nodeFamily_.sweep();
893
894 JLOG(m_journal.debug()) << "NodeFamily::FullBelowCache sweep. Size before: " << oldFullBelowSize
895 << "; size after: " << fullBelowCache->size();
896
897 JLOG(m_journal.debug()) << "NodeFamily::TreeNodeCache sweep. Size before: " << oldTreeNodeSize
898 << "; size after: " << treeNodeCache->size();
899 }
900 {
901 TaggedCache<uint256, Transaction> const& masterTxCache = getMasterTransaction().getCache();
902
903 std::size_t const oldMasterTxSize = masterTxCache.size();
904
905 getMasterTransaction().sweep();
906
907 JLOG(m_journal.debug()) << "MasterTransaction sweep. Size before: " << oldMasterTxSize
908 << "; size after: " << masterTxCache.size();
909 }
910 {
911 std::size_t const oldLedgerMasterCacheSize = getLedgerMaster().getFetchPackCacheSize();
912
913 getLedgerMaster().sweep();
914
915 JLOG(m_journal.debug()) << "LedgerMaster sweep. Size before: " << oldLedgerMasterCacheSize
916 << "; size after: " << getLedgerMaster().getFetchPackCacheSize();
917 }
918 {
919 // NodeCache == TaggedCache<SHAMapHash, Blob>
920 std::size_t const oldTempNodeCacheSize = getTempNodeCache().size();
921
922 getTempNodeCache().sweep();
923
924 JLOG(m_journal.debug()) << "TempNodeCache sweep. Size before: " << oldTempNodeCacheSize
925 << "; size after: " << getTempNodeCache().size();
926 }
927 {
928 std::size_t const oldCurrentCacheSize = getValidations().sizeOfCurrentCache();
929 std::size_t const oldSizeSeqEnforcesSize = getValidations().sizeOfSeqEnforcersCache();
930 std::size_t const oldByLedgerSize = getValidations().sizeOfByLedgerCache();
931 std::size_t const oldBySequenceSize = getValidations().sizeOfBySequenceCache();
932
933 getValidations().expire(m_journal);
934
935 JLOG(m_journal.debug()) << "Validations Current expire. Size before: " << oldCurrentCacheSize
936 << "; size after: " << getValidations().sizeOfCurrentCache();
937
938 JLOG(m_journal.debug()) << "Validations SeqEnforcer expire. Size before: " << oldSizeSeqEnforcesSize
939 << "; size after: " << getValidations().sizeOfSeqEnforcersCache();
940
941 JLOG(m_journal.debug()) << "Validations ByLedger expire. Size before: " << oldByLedgerSize
942 << "; size after: " << getValidations().sizeOfByLedgerCache();
943
944 JLOG(m_journal.debug()) << "Validations BySequence expire. Size before: " << oldBySequenceSize
945 << "; size after: " << getValidations().sizeOfBySequenceCache();
946 }
947 {
948 std::size_t const oldInboundLedgersSize = getInboundLedgers().cacheSize();
949
950 getInboundLedgers().sweep();
951
952 JLOG(m_journal.debug()) << "InboundLedgers sweep. Size before: " << oldInboundLedgersSize
953 << "; size after: " << getInboundLedgers().cacheSize();
954 }
955 {
956 size_t const oldTasksSize = getLedgerReplayer().tasksSize();
957 size_t const oldDeltasSize = getLedgerReplayer().deltasSize();
958 size_t const oldSkipListsSize = getLedgerReplayer().skipListsSize();
959
960 getLedgerReplayer().sweep();
961
962 JLOG(m_journal.debug()) << "LedgerReplayer tasks sweep. Size before: " << oldTasksSize
963 << "; size after: " << getLedgerReplayer().tasksSize();
964
965 JLOG(m_journal.debug()) << "LedgerReplayer deltas sweep. Size before: " << oldDeltasSize
966 << "; size after: " << getLedgerReplayer().deltasSize();
967
968 JLOG(m_journal.debug()) << "LedgerReplayer skipLists sweep. Size before: " << oldSkipListsSize
969 << "; size after: " << getLedgerReplayer().skipListsSize();
970 }
971 {
972 std::size_t const oldAcceptedLedgerSize = m_acceptedLedgerCache.size();
973
974 m_acceptedLedgerCache.sweep();
975
976 JLOG(m_journal.debug()) << "AcceptedLedgerCache sweep. Size before: " << oldAcceptedLedgerSize
977 << "; size after: " << m_acceptedLedgerCache.size();
978 }
979 {
980 std::size_t const oldCachedSLEsSize = cachedSLEs_.size();
981
982 cachedSLEs_.sweep();
983
984 JLOG(m_journal.debug()) << "CachedSLEs sweep. Size before: " << oldCachedSLEsSize
985 << "; size after: " << cachedSLEs_.size();
986 }
987
988 // Set timer to do another sweep later.
989 setSweepTimer();
990 }
991
994 {
995 return maxDisallowedLedger_;
996 }
997
998 virtual std::optional<uint256> const&
999 trapTxID() const override
1000 {
1001 return trapTxID_;
1002 }
1003
1004private:
1005 // For a newly-started validator, this is the greatest persisted ledger
1006 // and new validations must be greater than this.
1007 std::atomic<LedgerIndex> maxDisallowedLedger_{0};
1008
1009 void
1010 startGenesisLedger();
1011
1013 getLastFullLedger();
1014
1016 loadLedgerFromFile(std::string const& ledgerID);
1017
1018 bool
1019 loadOldLedger(std::string const& ledgerID, bool replay, bool isFilename, std::optional<uint256> trapTxID);
1020
1021 void
1022 setMaxDisallowedLedger();
1023
1025 app() override
1026 {
1027 return *this;
1028 }
1029};
1030
1031//------------------------------------------------------------------------------
1032
1033// TODO Break this up into smaller, more digestible initialization segments.
1034bool
1035ApplicationImp::setup(boost::program_options::variables_map const& cmdline)
1036{
1037 // We want to intercept CTRL-C and the standard termination signal SIGTERM
1038 // and terminate the process. This handler will NEVER be invoked twice.
1039 //
1040 // Note that async_wait is "one-shot": for each call, the handler will be
1041 // invoked exactly once, either when one of the registered signals in the
1042 // signal set occurs or the signal set is cancelled. Subsequent signals are
1043 // effectively ignored (technically, they are queued up, waiting for a call
1044 // to async_wait).
1045 m_signals.add(SIGINT);
1046 m_signals.add(SIGTERM);
1047 m_signals.async_wait([this](boost::system::error_code const& ec, int signum) {
1048 // Indicates the signal handler has been aborted; do nothing
1049 if (ec == boost::asio::error::operation_aborted)
1050 return;
1051
1052 JLOG(m_journal.info()) << "Received signal " << signum;
1053
1054 if (signum == SIGTERM || signum == SIGINT)
1055 signalStop("Signal: " + to_string(signum));
1056 });
1057
1058 auto debug_log = config_->getDebugLogFile();
1059
1060 if (!debug_log.empty())
1061 {
1062 // Let debug messages go to the file but only WARNING or higher to
1063 // regular output (unless verbose)
1064
1065 if (!logs_->open(debug_log))
1066 std::cerr << "Can't open log file " << debug_log << '\n';
1067
1068 using namespace beast::severities;
1069 if (logs_->threshold() > kDebug)
1070 logs_->threshold(kDebug);
1071 }
1072
1073 JLOG(m_journal.info()) << "Process starting: " << BuildInfo::getFullVersionString()
1074 << ", Instance Cookie: " << instanceCookie_;
1075
1076 if (numberOfThreads(*config_) < 2)
1077 {
1078 JLOG(m_journal.warn()) << "Limited to a single I/O service thread by "
1079 "system configuration.";
1080 }
1081
1082 // Optionally turn off logging to console.
1083 logs_->silent(config_->silent());
1084
1085 if (!initRelationalDatabase() || !initNodeStore())
1086 return false;
1087
1088 if (!peerReservations_->load(getWalletDB()))
1089 {
1090 JLOG(m_journal.fatal()) << "Cannot find peer reservations!";
1091 return false;
1092 }
1093
1094 if (validatorKeys_.keys)
1095 setMaxDisallowedLedger();
1096
1097 // Configure the amendments the server supports
1098 {
1099 auto const supported = []() {
1100 auto const& amendments = detail::supportedAmendments();
1102 supported.reserve(amendments.size());
1103 for (auto const& [a, vote] : amendments)
1104 {
1105 auto const f = xrpl::getRegisteredFeature(a);
1106 XRPL_ASSERT(f, "xrpl::ApplicationImp::setup : registered feature");
1107 if (f)
1108 supported.emplace_back(a, *f, vote);
1109 }
1110 return supported;
1111 }();
1112 Section const& downVoted = config_->section(SECTION_VETO_AMENDMENTS);
1113
1114 Section const& upVoted = config_->section(SECTION_AMENDMENTS);
1115
1116 m_amendmentTable = make_AmendmentTable(
1117 *this, config().AMENDMENT_MAJORITY_TIME, supported, upVoted, downVoted, logs_->journal("Amendments"));
1118 }
1119
1120 Pathfinder::initPathTable();
1121
1122 auto const startUp = config_->START_UP;
1123 JLOG(m_journal.debug()) << "startUp: " << startUp;
1124 if (startUp == StartUpType::FRESH)
1125 {
1126 JLOG(m_journal.info()) << "Starting new Ledger";
1127
1128 startGenesisLedger();
1129 }
1130 else if (startUp == StartUpType::LOAD || startUp == StartUpType::LOAD_FILE || startUp == StartUpType::REPLAY)
1131 {
1132 JLOG(m_journal.info()) << "Loading specified Ledger";
1133
1134 if (!loadOldLedger(
1135 config_->START_LEDGER,
1136 startUp == StartUpType::REPLAY,
1137 startUp == StartUpType::LOAD_FILE,
1138 config_->TRAP_TX_HASH))
1139 {
1140 JLOG(m_journal.error()) << "The specified ledger could not be loaded.";
1141 if (config_->FAST_LOAD)
1142 {
1143 // Fall back to syncing from the network, such as
1144 // when there's no existing data.
1145 startGenesisLedger();
1146 }
1147 else
1148 {
1149 return false;
1150 }
1151 }
1152 }
1153 else if (startUp == StartUpType::NETWORK)
1154 {
1155 // This should probably become the default once we have a stable
1156 // network.
1157 if (!config_->standalone())
1158 m_networkOPs->setNeedNetworkLedger();
1159
1160 startGenesisLedger();
1161 }
1162 else
1163 {
1164 startGenesisLedger();
1165 }
1166
1167 if (auto const& forcedRange = config().FORCED_LEDGER_RANGE_PRESENT)
1168 {
1169 m_ledgerMaster->setLedgerRangePresent(forcedRange->first, forcedRange->second);
1170 }
1171
1172 m_orderBookDB.setup(getLedgerMaster().getCurrentLedger());
1173
1174 nodeIdentity_ = getNodeIdentity(*this, cmdline);
1175
1176 if (!cluster_->load(config().section(SECTION_CLUSTER_NODES)))
1177 {
1178 JLOG(m_journal.fatal()) << "Invalid entry in cluster configuration.";
1179 return false;
1180 }
1181
1182 {
1183 if (validatorKeys_.configInvalid())
1184 return false;
1185
1186 if (!validatorManifests_->load(
1187 getWalletDB(),
1188 "ValidatorManifests",
1189 validatorKeys_.manifest,
1190 config().section(SECTION_VALIDATOR_KEY_REVOCATION).values()))
1191 {
1192 JLOG(m_journal.fatal()) << "Invalid configured validator manifest.";
1193 return false;
1194 }
1195
1196 publisherManifests_->load(getWalletDB(), "PublisherManifests");
1197
1198 // It is possible to have a valid ValidatorKeys object without
1199 // setting the signingKey or masterKey. This occurs if the
1200 // configuration file does not have either
1201 // SECTION_VALIDATOR_TOKEN or SECTION_VALIDATION_SEED section.
1202
1203 // masterKey for the configuration-file specified validator keys
1204 std::optional<PublicKey> localSigningKey;
1205 if (validatorKeys_.keys)
1206 localSigningKey = validatorKeys_.keys->publicKey;
1207
1208 // Setup trusted validators
1209 if (!validators_->load(
1210 localSigningKey,
1211 config().section(SECTION_VALIDATORS).values(),
1212 config().section(SECTION_VALIDATOR_LIST_KEYS).values(),
1213 config().VALIDATOR_LIST_THRESHOLD))
1214 {
1215 JLOG(m_journal.fatal()) << "Invalid entry in validator configuration.";
1216 return false;
1217 }
1218 }
1219
1220 if (!validatorSites_->load(config().section(SECTION_VALIDATOR_LIST_SITES).values()))
1221 {
1222 JLOG(m_journal.fatal()) << "Invalid entry in [" << SECTION_VALIDATOR_LIST_SITES << "]";
1223 return false;
1224 }
1225
1226 // Tell the AmendmentTable who the trusted validators are.
1227 m_amendmentTable->trustChanged(validators_->getQuorumKeys().second);
1228
1229 //----------------------------------------------------------------------
1230 //
1231 // Server
1232 //
1233 //----------------------------------------------------------------------
1234
1235 // VFALCO NOTE Unfortunately, in stand-alone mode some code still
1236 // foolishly calls overlay(). When this is fixed we can
1237 // move the instantiation inside a conditional:
1238 //
1239 // if (!config_.standalone())
1240 overlay_ = make_Overlay(
1241 *this,
1242 setup_Overlay(*config_),
1243 *serverHandler_,
1244 *m_resourceManager,
1245 *m_resolver,
1246 get_io_context(),
1247 *config_,
1248 m_collectorManager->collector());
1249 add(*overlay_); // add to PropertyStream
1250
1251 // start first consensus round
1252 if (!m_networkOPs->beginConsensus(m_ledgerMaster->getClosedLedger()->header().hash, {}))
1253 {
1254 JLOG(m_journal.fatal()) << "Unable to start consensus";
1255 return false;
1256 }
1257
1258 {
1259 try
1260 {
1261 auto setup = setup_ServerHandler(*config_, beast::logstream{m_journal.error()});
1262 setup.makeContexts();
1263 serverHandler_->setup(setup, m_journal);
1264 fixConfigPorts(*config_, serverHandler_->endpoints());
1265 }
1266 catch (std::exception const& e)
1267 {
1268 if (auto stream = m_journal.fatal())
1269 {
1270 stream << "Unable to setup server handler";
1271 if (std::strlen(e.what()) > 0)
1272 stream << ": " << e.what();
1273 }
1274 return false;
1275 }
1276 }
1277
1278 // Begin connecting to network.
1279 if (!config_->standalone())
1280 {
1281 // Should this message be here, conceptually? In theory this sort
1282 // of message, if displayed, should be displayed from PeerFinder.
1283 if (config_->PEER_PRIVATE && config_->IPS_FIXED.empty())
1284 {
1285 JLOG(m_journal.warn()) << "No outbound peer connections will be made";
1286 }
1287
1288 // VFALCO NOTE the state timer resets the deadlock detector.
1289 //
1290 m_networkOPs->setStateTimer();
1291 }
1292 else
1293 {
1294 JLOG(m_journal.warn()) << "Running in standalone mode";
1295
1296 m_networkOPs->setStandAlone();
1297 }
1298
1299 if (config_->canSign())
1300 {
1301 JLOG(m_journal.warn()) << "*** The server is configured to allow the "
1302 "'sign' and 'sign_for'";
1303 JLOG(m_journal.warn()) << "*** commands. These commands have security "
1304 "implications and have";
1305 JLOG(m_journal.warn()) << "*** been deprecated. They will be removed "
1306 "in a future release of";
1307 JLOG(m_journal.warn()) << "*** rippled.";
1308 JLOG(m_journal.warn()) << "*** If you do not use them to sign "
1309 "transactions please edit your";
1310 JLOG(m_journal.warn()) << "*** configuration file and remove the [enable_signing] stanza.";
1311 JLOG(m_journal.warn()) << "*** If you do use them to sign transactions "
1312 "please migrate to a";
1313 JLOG(m_journal.warn()) << "*** standalone signing solution as soon as possible.";
1314 }
1315
1316 //
1317 // Execute start up rpc commands.
1318 //
1319 for (auto cmd : config_->section(SECTION_RPC_STARTUP).lines())
1320 {
1321 Json::Reader jrReader;
1322 Json::Value jvCommand;
1323
1324 if (!jrReader.parse(cmd, jvCommand))
1325 {
1326 JLOG(m_journal.fatal()) << "Couldn't parse entry in [" << SECTION_RPC_STARTUP << "]: '" << cmd;
1327 }
1328
1329 if (!config_->quiet())
1330 {
1331 JLOG(m_journal.fatal()) << "Startup RPC: " << jvCommand << std::endl;
1332 }
1333
1334 Resource::Charge loadType = Resource::feeReferenceRPC;
1336 RPC::JsonContext context{
1337 {journal("RPCHandler"),
1338 *this,
1339 loadType,
1340 getOPs(),
1341 getLedgerMaster(),
1342 c,
1343 Role::ADMIN,
1344 {},
1345 {},
1346 RPC::apiMaximumSupportedVersion},
1347 jvCommand};
1348
1349 Json::Value jvResult;
1350 RPC::doCommand(context, jvResult);
1351
1352 if (!config_->quiet())
1353 {
1354 JLOG(m_journal.fatal()) << "Result: " << jvResult << std::endl;
1355 }
1356 }
1357
1358 validatorSites_->start();
1359
1360 return true;
1361}
1362
1363void
1364ApplicationImp::start(bool withTimers)
1365{
1366 JLOG(m_journal.info()) << "Application starting. Version is " << BuildInfo::getVersionString();
1367
1368 if (withTimers)
1369 {
1370 setSweepTimer();
1371 setEntropyTimer();
1372 }
1373
1374 m_io_latency_sampler.start();
1375 m_resolver->start();
1376 m_loadManager->start();
1377 m_shaMapStore->start();
1378 if (overlay_)
1379 overlay_->start();
1380
1381 if (grpcServer_->start())
1382 fixConfigPorts(*config_, {{SECTION_PORT_GRPC, grpcServer_->getEndpoint()}});
1383
1384 ledgerCleaner_->start();
1385 perfLog_->start();
1386}
1387
1388void
1389ApplicationImp::run()
1390{
1391 if (!config_->standalone())
1392 {
1393 // VFALCO NOTE This seems unnecessary. If we properly refactor the load
1394 // manager then the stall detector can just always be
1395 // "armed"
1396 //
1397 getLoadManager().activateStallDetector();
1398 }
1399
1400 isTimeToStop.wait(false, std::memory_order_relaxed);
1401
1402 JLOG(m_journal.debug()) << "Application stopping";
1403
1404 m_io_latency_sampler.cancel_async();
1405
1406 // VFALCO Enormous hack, we have to force the probe to cancel
1407 // before we stop the io_context queue or else it never
1408 // unblocks in its destructor. The fix is to make all
1409 // io_objects gracefully handle exit so that we can
1410 // naturally return from io_context::run() instead of
1411 // forcing a call to io_context::stop()
1412 m_io_latency_sampler.cancel();
1413
1414 m_resolver->stop_async();
1415
1416 // NIKB This is a hack - we need to wait for the resolver to
1417 // stop. before we stop the io_server_queue or weird
1418 // things will happen.
1419 m_resolver->stop();
1420
1421 {
1422 try
1423 {
1424 sweepTimer_.cancel();
1425 }
1426 catch (boost::system::system_error const& e)
1427 {
1428 JLOG(m_journal.error()) << "Application: sweepTimer cancel error: " << e.what();
1429 }
1430
1431 try
1432 {
1433 entropyTimer_.cancel();
1434 }
1435 catch (boost::system::system_error const& e)
1436 {
1437 JLOG(m_journal.error()) << "Application: entropyTimer cancel error: " << e.what();
1438 }
1439 }
1440
1441 // Make sure that any waitHandlers pending in our timers are done
1442 // before we declare ourselves stopped.
1443 using namespace std::chrono_literals;
1444
1445 waitHandlerCounter_.join("Application", 1s, m_journal);
1446
1447 mValidations.flush();
1448
1449 validatorSites_->stop();
1450
1451 // TODO Store manifests in manifests.sqlite instead of wallet.db
1452 validatorManifests_->save(
1453 getWalletDB(), "ValidatorManifests", [this](PublicKey const& pubKey) { return validators().listed(pubKey); });
1454
1455 publisherManifests_->save(getWalletDB(), "PublisherManifests", [this](PublicKey const& pubKey) {
1456 return validators().trustedPublisher(pubKey);
1457 });
1458
1459 // The order of these stop calls is delicate.
1460 // Re-ordering them risks undefined behavior.
1461 m_loadManager->stop();
1462 m_shaMapStore->stop();
1463 m_jobQueue->stop();
1464 if (overlay_)
1465 overlay_->stop();
1466 grpcServer_->stop();
1467 m_networkOPs->stop();
1468 serverHandler_->stop();
1469 m_ledgerReplayer->stop();
1470 m_inboundTransactions->stop();
1471 m_inboundLedgers->stop();
1472 ledgerCleaner_->stop();
1473 m_nodeStore->stop();
1474 perfLog_->stop();
1475
1476 JLOG(m_journal.info()) << "Done.";
1477}
1478
1479void
1480ApplicationImp::signalStop(std::string msg)
1481{
1482 if (!isTimeToStop.test_and_set(std::memory_order_acquire))
1483 {
1484 if (msg.empty())
1485 JLOG(m_journal.warn()) << "Server stopping";
1486 else
1487 JLOG(m_journal.warn()) << "Server stopping: " << msg;
1488
1489 isTimeToStop.notify_all();
1490 }
1491}
1492
1493bool
1494ApplicationImp::checkSigs() const
1495{
1496 return checkSigs_;
1497}
1498
1499void
1500ApplicationImp::checkSigs(bool check)
1501{
1502 checkSigs_ = check;
1503}
1504
1505bool
1506ApplicationImp::isStopping() const
1507{
1508 return isTimeToStop.test(std::memory_order_relaxed);
1509}
1510
1511int
1512ApplicationImp::fdRequired() const
1513{
1514 // Standard handles, config file, misc I/O etc:
1515 int needed = 128;
1516
1517 // 2x the configured peer limit for peer connections:
1518 if (overlay_)
1519 needed += 2 * overlay_->limit();
1520
1521 // the number of fds needed by the backend (internally
1522 // doubled if online delete is enabled).
1523 needed += std::max(5, m_shaMapStore->fdRequired());
1524
1525 // One fd per incoming connection a port can accept, or
1526 // if no limit is set, assume it'll handle 256 clients.
1527 for (auto const& p : serverHandler_->setup().ports)
1528 needed += std::max(256, p.limit);
1529
1530 // The minimum number of file descriptors we need is 1024:
1531 return std::max(1024, needed);
1532}
1533
1534//------------------------------------------------------------------------------
1535
1536void
1537ApplicationImp::startGenesisLedger()
1538{
1539 std::vector<uint256> const initialAmendments =
1540 (config_->START_UP == StartUpType::FRESH) ? m_amendmentTable->getDesired() : std::vector<uint256>{};
1541
1542 std::shared_ptr<Ledger> const genesis =
1543 std::make_shared<Ledger>(create_genesis, *config_, initialAmendments, nodeFamily_);
1544 m_ledgerMaster->storeLedger(genesis);
1545
1546 auto const next = std::make_shared<Ledger>(*genesis, timeKeeper().closeTime());
1547 next->updateSkipList();
1548 XRPL_ASSERT(
1549 next->header().seq < XRP_LEDGER_EARLIEST_FEES || next->read(keylet::fees()),
1550 "xrpl::ApplicationImp::startGenesisLedger : valid ledger fees");
1551 next->setImmutable();
1552 openLedger_.emplace(next, cachedSLEs_, logs_->journal("OpenLedger"));
1553 m_ledgerMaster->storeLedger(next);
1554 m_ledgerMaster->switchLCL(next);
1555}
1556
1558ApplicationImp::getLastFullLedger()
1559{
1560 auto j = journal("Ledger");
1561
1562 try
1563 {
1564 auto const [ledger, seq, hash] = getLatestLedger(*this);
1565
1566 if (!ledger)
1567 return ledger;
1568
1569 XRPL_ASSERT(
1570 ledger->header().seq < XRP_LEDGER_EARLIEST_FEES || ledger->read(keylet::fees()),
1571 "xrpl::ApplicationImp::getLastFullLedger : valid ledger fees");
1572 ledger->setImmutable();
1573
1574 if (getLedgerMaster().haveLedger(seq))
1575 ledger->setValidated();
1576
1577 if (ledger->header().hash == hash)
1578 {
1579 JLOG(j.trace()) << "Loaded ledger: " << hash;
1580 return ledger;
1581 }
1582
1583 if (auto stream = j.error())
1584 {
1585 stream << "Failed on ledger";
1586 Json::Value p;
1587 addJson(p, {*ledger, nullptr, LedgerFill::full});
1588 stream << p;
1589 }
1590
1591 return {};
1592 }
1593 catch (SHAMapMissingNode const& mn)
1594 {
1595 JLOG(j.warn()) << "Ledger in database: " << mn.what();
1596 return {};
1597 }
1598}
1599
1601ApplicationImp::loadLedgerFromFile(std::string const& name)
1602{
1603 try
1604 {
1605 std::ifstream ledgerFile(name, std::ios::in);
1606
1607 if (!ledgerFile)
1608 {
1609 JLOG(m_journal.fatal()) << "Unable to open file '" << name << "'";
1610 return nullptr;
1611 }
1612
1613 Json::Reader reader;
1614 Json::Value jLedger;
1615
1616 if (!reader.parse(ledgerFile, jLedger))
1617 {
1618 JLOG(m_journal.fatal()) << "Unable to parse ledger JSON";
1619 return nullptr;
1620 }
1621
1623
1624 // accept a wrapped ledger
1625 if (ledger.get().isMember("result"))
1626 ledger = ledger.get()["result"];
1627
1628 if (ledger.get().isMember("ledger"))
1629 ledger = ledger.get()["ledger"];
1630
1631 std::uint32_t seq = 1;
1632 auto closeTime = timeKeeper().closeTime();
1633 using namespace std::chrono_literals;
1634 auto closeTimeResolution = 30s;
1635 bool closeTimeEstimated = false;
1636 std::uint64_t totalDrops = 0;
1637
1638 if (ledger.get().isMember("accountState"))
1639 {
1640 if (ledger.get().isMember(jss::ledger_index))
1641 {
1642 seq = ledger.get()[jss::ledger_index].asUInt();
1643 }
1644
1645 if (ledger.get().isMember("close_time"))
1646 {
1647 using tp = NetClock::time_point;
1648 using d = tp::duration;
1649 closeTime = tp{d{ledger.get()["close_time"].asUInt()}};
1650 }
1651 if (ledger.get().isMember("close_time_resolution"))
1652 {
1653 using namespace std::chrono;
1654 closeTimeResolution = seconds{ledger.get()["close_time_resolution"].asUInt()};
1655 }
1656 if (ledger.get().isMember("close_time_estimated"))
1657 {
1658 closeTimeEstimated = ledger.get()["close_time_estimated"].asBool();
1659 }
1660 if (ledger.get().isMember("total_coins"))
1661 {
1662 totalDrops = beast::lexicalCastThrow<std::uint64_t>(ledger.get()["total_coins"].asString());
1663 }
1664
1665 ledger = ledger.get()["accountState"];
1666 }
1667
1668 if (!ledger.get().isArrayOrNull())
1669 {
1670 JLOG(m_journal.fatal()) << "State nodes must be an array";
1671 return nullptr;
1672 }
1673
1674 auto loadLedger = std::make_shared<Ledger>(seq, closeTime, *config_, nodeFamily_);
1675 loadLedger->setTotalDrops(totalDrops);
1676
1677 for (Json::UInt index = 0; index < ledger.get().size(); ++index)
1678 {
1679 Json::Value& entry = ledger.get()[index];
1680
1681 if (!entry.isObjectOrNull())
1682 {
1683 JLOG(m_journal.fatal()) << "Invalid entry in ledger";
1684 return nullptr;
1685 }
1686
1687 uint256 uIndex;
1688
1689 if (!uIndex.parseHex(entry[jss::index].asString()))
1690 {
1691 JLOG(m_journal.fatal()) << "Invalid entry in ledger";
1692 return nullptr;
1693 }
1694
1695 entry.removeMember(jss::index);
1696
1697 STParsedJSONObject stp("sle", ledger.get()[index]);
1698
1699 if (!stp.object || uIndex.isZero())
1700 {
1701 JLOG(m_journal.fatal()) << "Invalid entry in ledger";
1702 return nullptr;
1703 }
1704
1705 // VFALCO TODO This is the only place that
1706 // constructor is used, try to remove it
1707 STLedgerEntry sle(*stp.object, uIndex);
1708
1709 if (!loadLedger->addSLE(sle))
1710 {
1711 JLOG(m_journal.fatal()) << "Couldn't add serialized ledger: " << uIndex;
1712 return nullptr;
1713 }
1714 }
1715
1716 loadLedger->stateMap().flushDirty(hotACCOUNT_NODE);
1717
1718 XRPL_ASSERT(
1719 loadLedger->header().seq < XRP_LEDGER_EARLIEST_FEES || loadLedger->read(keylet::fees()),
1720 "xrpl::ApplicationImp::loadLedgerFromFile : valid ledger fees");
1721 loadLedger->setAccepted(closeTime, closeTimeResolution, !closeTimeEstimated);
1722
1723 return loadLedger;
1724 }
1725 catch (std::exception const& x)
1726 {
1727 JLOG(m_journal.fatal()) << "Ledger contains invalid data: " << x.what();
1728 return nullptr;
1729 }
1730}
1731
1732bool
1733ApplicationImp::loadOldLedger(
1734 std::string const& ledgerID,
1735 bool replay,
1736 bool isFileName,
1737 std::optional<uint256> trapTxID)
1738{
1739 try
1740 {
1741 std::shared_ptr<Ledger const> loadLedger, replayLedger;
1742
1743 if (isFileName)
1744 {
1745 if (!ledgerID.empty())
1746 loadLedger = loadLedgerFromFile(ledgerID);
1747 }
1748 else if (ledgerID.length() == 64)
1749 {
1750 uint256 hash;
1751
1752 if (hash.parseHex(ledgerID))
1753 {
1754 loadLedger = loadByHash(hash, *this);
1755
1756 if (!loadLedger)
1757 {
1758 // Try to build the ledger from the back end
1760 *this, hash, 0, InboundLedger::Reason::GENERIC, stopwatch(), make_DummyPeerSet(*this));
1761 if (il->checkLocal())
1762 loadLedger = il->getLedger();
1763 }
1764 }
1765 }
1766 else if (ledgerID.empty() || boost::iequals(ledgerID, "latest"))
1767 {
1768 loadLedger = getLastFullLedger();
1769 }
1770 else
1771 {
1772 // assume by sequence
1773 std::uint32_t index;
1774
1775 if (beast::lexicalCastChecked(index, ledgerID))
1776 loadLedger = loadByIndex(index, *this);
1777 }
1778
1779 if (!loadLedger)
1780 return false;
1781
1782 if (replay)
1783 {
1784 // Replay a ledger close with same prior ledger and transactions
1785
1786 // this ledger holds the transactions we want to replay
1787 replayLedger = loadLedger;
1788
1789 JLOG(m_journal.info()) << "Loading parent ledger";
1790
1791 loadLedger = loadByHash(replayLedger->header().parentHash, *this);
1792 if (!loadLedger)
1793 {
1794 JLOG(m_journal.info()) << "Loading parent ledger from node store";
1795
1796 // Try to build the ledger from the back end
1798 *this,
1799 replayLedger->header().parentHash,
1800 0,
1801 InboundLedger::Reason::GENERIC,
1802 stopwatch(),
1803 make_DummyPeerSet(*this));
1804
1805 if (il->checkLocal())
1806 loadLedger = il->getLedger();
1807
1808 if (!loadLedger)
1809 {
1810 // LCOV_EXCL_START
1811 JLOG(m_journal.fatal()) << "Replay ledger missing/damaged";
1812 UNREACHABLE(
1813 "xrpl::ApplicationImp::loadOldLedger : replay ledger "
1814 "missing/damaged");
1815 return false;
1816 // LCOV_EXCL_STOP
1817 }
1818 }
1819 }
1820 using namespace std::chrono_literals;
1821 using namespace date;
1822 static constexpr NetClock::time_point ledgerWarnTimePoint{
1823 sys_days{January / 1 / 2018} - sys_days{January / 1 / 2000}};
1824 if (loadLedger->header().closeTime < ledgerWarnTimePoint)
1825 {
1826 JLOG(m_journal.fatal()) << "\n\n*** WARNING ***\n"
1827 "You are replaying a ledger from before "
1828 << to_string(ledgerWarnTimePoint)
1829 << " UTC.\n"
1830 "This replay will not handle your ledger as it was "
1831 "originally "
1832 "handled.\nConsider running an earlier version of rippled "
1833 "to "
1834 "get the older rules.\n*** CONTINUING ***\n";
1835 }
1836
1837 JLOG(m_journal.info()) << "Loading ledger " << loadLedger->header().hash << " seq:" << loadLedger->header().seq;
1838
1839 if (loadLedger->header().accountHash.isZero())
1840 {
1841 // LCOV_EXCL_START
1842 JLOG(m_journal.fatal()) << "Ledger is empty.";
1843 UNREACHABLE("xrpl::ApplicationImp::loadOldLedger : ledger is empty");
1844 return false;
1845 // LCOV_EXCL_STOP
1846 }
1847
1848 if (!loadLedger->walkLedger(journal("Ledger"), true))
1849 {
1850 // LCOV_EXCL_START
1851 JLOG(m_journal.fatal()) << "Ledger is missing nodes.";
1852 UNREACHABLE(
1853 "xrpl::ApplicationImp::loadOldLedger : ledger is missing "
1854 "nodes");
1855 return false;
1856 // LCOV_EXCL_STOP
1857 }
1858
1859 if (!loadLedger->assertSensible(journal("Ledger")))
1860 {
1861 // LCOV_EXCL_START
1862 JLOG(m_journal.fatal()) << "Ledger is not sensible.";
1863 UNREACHABLE(
1864 "xrpl::ApplicationImp::loadOldLedger : ledger is not "
1865 "sensible");
1866 return false;
1867 // LCOV_EXCL_STOP
1868 }
1869
1870 m_ledgerMaster->setLedgerRangePresent(loadLedger->header().seq, loadLedger->header().seq);
1871
1872 m_ledgerMaster->switchLCL(loadLedger);
1873 loadLedger->setValidated();
1874 m_ledgerMaster->setFullLedger(loadLedger, true, false);
1875 openLedger_.emplace(loadLedger, cachedSLEs_, logs_->journal("OpenLedger"));
1876
1877 if (replay)
1878 {
1879 // inject transaction(s) from the replayLedger into our open ledger
1880 // and build replay structure
1881 auto replayData = std::make_unique<LedgerReplay>(loadLedger, replayLedger);
1882
1883 for (auto const& [_, tx] : replayData->orderedTxns())
1884 {
1885 (void)_;
1886 auto txID = tx->getTransactionID();
1887 if (trapTxID == txID)
1888 {
1889 trapTxID_ = txID;
1890 JLOG(m_journal.debug()) << "Trap transaction set: " << txID;
1891 }
1892
1894 tx->add(*s);
1895
1896 forceValidity(getHashRouter(), txID, Validity::SigGoodOnly);
1897
1898 openLedger_->modify([&txID, &s](OpenView& view, beast::Journal j) {
1899 view.rawTxInsert(txID, std::move(s), nullptr);
1900 return true;
1901 });
1902 }
1903
1904 m_ledgerMaster->takeReplay(std::move(replayData));
1905
1906 if (trapTxID && !trapTxID_)
1907 {
1908 JLOG(m_journal.fatal()) << "Ledger " << replayLedger->header().seq
1909 << " does not contain the transaction hash " << *trapTxID;
1910 return false;
1911 }
1912 }
1913 }
1914 catch (SHAMapMissingNode const& mn)
1915 {
1916 JLOG(m_journal.fatal()) << "While loading specified ledger: " << mn.what();
1917 return false;
1918 }
1919 catch (boost::bad_lexical_cast&)
1920 {
1921 JLOG(m_journal.fatal()) << "Ledger specified '" << ledgerID << "' is not valid";
1922 return false;
1923 }
1924
1925 return true;
1926}
1927
1928bool
1929ApplicationImp::serverOkay(std::string& reason)
1930{
1931 if (!config().ELB_SUPPORT)
1932 return true;
1933
1934 if (isStopping())
1935 {
1936 reason = "Server is shutting down";
1937 return false;
1938 }
1939
1940 if (getOPs().isNeedNetworkLedger())
1941 {
1942 reason = "Not synchronized with network yet";
1943 return false;
1944 }
1945
1946 if (getOPs().isAmendmentBlocked())
1947 {
1948 reason = "Server version too old";
1949 return false;
1950 }
1951
1952 if (getOPs().isUNLBlocked())
1953 {
1954 reason = "No valid validator list available";
1955 return false;
1956 }
1957
1958 if (getOPs().getOperatingMode() < OperatingMode::SYNCING)
1959 {
1960 reason = "Not synchronized with network";
1961 return false;
1962 }
1963
1964 if (!getLedgerMaster().isCaughtUp(reason))
1965 return false;
1966
1967 if (getFeeTrack().isLoadedLocal())
1968 {
1969 reason = "Too much load";
1970 return false;
1971 }
1972
1973 return true;
1974}
1975
1977ApplicationImp::journal(std::string const& name)
1978{
1979 return logs_->journal(name);
1980}
1981
1982void
1983ApplicationImp::setMaxDisallowedLedger()
1984{
1985 auto seq = getRelationalDatabase().getMaxLedgerSeq();
1986 if (seq)
1987 maxDisallowedLedger_ = *seq;
1988
1989 JLOG(m_journal.trace()) << "Max persisted ledger is " << maxDisallowedLedger_;
1990}
1991
1992//------------------------------------------------------------------------------
1993
1994Application::Application() : beast::PropertyStream::Source("app")
1995{
1996}
1997
1998//------------------------------------------------------------------------------
1999
2002{
2003 return std::make_unique<ApplicationImp>(std::move(config), std::move(logs), std::move(timeKeeper));
2004}
2005
2006void
2007fixConfigPorts(Config& config, Endpoints const& endpoints)
2008{
2009 for (auto const& [name, ep] : endpoints)
2010 {
2011 if (!config.exists(name))
2012 continue;
2013
2014 auto& section = config[name];
2015 auto const optPort = section.get("port");
2016 if (optPort)
2017 {
2018 std::uint16_t const port = beast::lexicalCast<std::uint16_t>(*optPort);
2019 if (!port)
2020 section.set("port", std::to_string(ep.port()));
2021 }
2022 }
2023}
2024
2025} // namespace xrpl
boost::asio::io_context & get_io_context()
Definition BasicApp.h:22
Unserialize a JSON document into a Value.
Definition json_reader.h:17
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
Represents a JSON value.
Definition json_value.h:130
bool isObjectOrNull() const
Value removeMember(char const *key)
Remove and return the named member.
std::string asString() const
Returns the unquoted string value.
A generic endpoint for log messages.
Definition Journal.h:40
Stream fatal() const
Definition Journal.h:324
Stream warn() const
Definition Journal.h:312
std::string const & name() const
Returns the name of this source.
void add(Source &source)
Add a child source.
Abstract stream with RAII containers that produce a property tree.
A metric for reporting event timing.
Definition Event.h:21
void notify(std::chrono::duration< Rep, Period > const &value) const
Push an event notification.
Definition Event.h:44
Measures handler latency on an io_context queue.
void sample(Handler &&handler)
Initiate continuous i/o latency sampling.
void cancel()
Cancel all pending i/o.
The amendment table stores the list of enabled and potential amendments.
beast::io_latency_probe< std::chrono::steady_clock > m_probe
void operator()(Duration const &elapsed)
std::atomic< std::chrono::milliseconds > lastSample_
io_latency_sampler(beast::insight::Event ev, beast::Journal journal, std::chrono::milliseconds interval, boost::asio::io_context &ios)
std::chrono::milliseconds get() const
LedgerReplayer & getLedgerReplayer() override
Application::MutexType & getMasterMutex() override
std::optional< std::pair< PublicKey, SecretKey > > nodeIdentity_
InboundLedgers & getInboundLedgers() override
std::unique_ptr< LedgerCleaner > ledgerCleaner_
std::unique_ptr< LoadManager > m_loadManager
void start(bool withTimers) override
LoadFeeTrack & getFeeTrack() override
Cluster & cluster() override
std::unique_ptr< perf::PerfLog > perfLog_
std::unique_ptr< HashRouter > hashRouter_
std::optional< OpenLedger > openLedger_
RCLValidations & getValidations() override
void run() override
OpenLedger & openLedger() override
Resource::Manager & getResourceManager() override
NodeStoreScheduler m_nodeStoreScheduler
ClosureCounter< void, boost::system::error_code const & > waitHandlerCounter_
beast::Journal m_journal
RelationalDatabase & getRelationalDatabase() override
TransactionMaster & getMasterTransaction() override
std::chrono::milliseconds getIOLatency() override
std::unique_ptr< CollectorManager > m_collectorManager
boost::asio::io_context & getIOContext() override
std::optional< PublicKey const > getValidationPublicKey() const override
HashRouter & getHashRouter() override
LoadManager & getLoadManager() override
PendingSaves pendingSaves_
std::unique_ptr< RelationalDatabase > mRelationalDatabase
std::atomic< bool > checkSigs_
bool checkSigs() const override
bool serverOkay(std::string &reason) override
std::unique_ptr< SHAMapStore > m_shaMapStore
Application::MutexType m_masterMutex
InboundTransactions & getInboundTransactions() override
io_latency_sampler m_io_latency_sampler
std::unique_ptr< AmendmentTable > m_amendmentTable
std::unique_ptr< InboundTransactions > m_inboundTransactions
SHAMapStore & getSHAMapStore() override
boost::asio::steady_timer sweepTimer_
std::unique_ptr< NodeStore::Database > m_nodeStore
std::unique_ptr< LoadFeeTrack > mFeeTrack
boost::asio::steady_timer entropyTimer_
std::unique_ptr< Overlay > overlay_
bool setup(boost::program_options::variables_map const &cmdline) override
Overlay & overlay() override
std::unique_ptr< Config > config_
ManifestCache & publisherManifests() override
std::unique_ptr< ManifestCache > validatorManifests_
CollectorManager & getCollectorManager() override
std::optional< uint256 > trapTxID_
std::unique_ptr< ResolverAsio > m_resolver
NetworkOPs & getOPs() override
TimeKeeper & timeKeeper() override
std::unique_ptr< ValidatorList > validators_
std::unique_ptr< TxQ > txQ_
static std::size_t numberOfThreads(Config const &config)
OpenLedger const & openLedger() const override
std::unique_ptr< ManifestCache > publisherManifests_
LedgerIndex getMaxDisallowedLedger() override
Ensure that a newly-started validator does not sign proposals older than the last ledger it persisted...
NodeCache & getTempNodeCache() override
Application & app() override
Logs & logs() override
OrderBookDB m_orderBookDB
ValidatorList & validators() override
PeerReservationTable & peerReservations() override
ApplicationImp(std::unique_ptr< Config > config, std::unique_ptr< Logs > logs, std::unique_ptr< TimeKeeper > timeKeeper)
std::unique_ptr< PeerReservationTable > peerReservations_
std::unique_ptr< Resource::Manager > m_resourceManager
std::pair< PublicKey, SecretKey > const & nodeIdentity() override
std::unique_ptr< Logs > logs_
std::unique_ptr< DatabaseCon > mWalletDB
CachedSLEs & cachedSLEs() override
ValidatorSite & validatorSites() override
virtual std::optional< uint256 > const & trapTxID() const override
std::atomic_flag isTimeToStop
std::unique_ptr< LedgerMaster > m_ledgerMaster
std::unique_ptr< GRPCServer > grpcServer_
std::unique_ptr< ServerHandler > serverHandler_
ValidatorKeys const validatorKeys_
std::uint64_t instanceID() const override
Returns a 64-bit instance identifier, generated at startup.
OrderBookDB & getOrderBookDB() override
Family & getNodeFamily() override
void gotTXSet(std::shared_ptr< SHAMap > const &set, bool fromAcquire)
Config & config() override
DatabaseCon & getWalletDB() override
Retrieve the "wallet database".
PathRequests & getPathRequests() override
bool isStopping() const override
std::unique_ptr< TimeKeeper > timeKeeper_
beast::Journal journal(std::string const &name) override
std::unique_ptr< ValidatorSite > validatorSites_
RCLValidations mValidations
void signalStop(std::string msg) override
std::uint64_t const instanceCookie_
LedgerCleaner & getLedgerCleaner() override
ManifestCache & validatorManifests() override
AmendmentTable & getAmendmentTable() override
int fdRequired() const override
TaggedCache< uint256, AcceptedLedger > m_acceptedLedgerCache
std::unique_ptr< PathRequests > m_pathRequests
std::unique_ptr< JobQueue > m_jobQueue
LedgerMaster & getLedgerMaster() override
NodeStore::Database & getNodeStore() override
TransactionMaster m_txMaster
std::unique_ptr< Cluster > cluster_
std::unique_ptr< NetworkOPs > m_networkOPs
virtual ServerHandler & getServerHandler() override
JobQueue & getJobQueue() override
PendingSaves & pendingSaves() override
void onWrite(beast::PropertyStream::Map &stream) override
Subclass override.
std::unique_ptr< InboundLedgers > m_inboundLedgers
TaggedCache< uint256, AcceptedLedger > & getAcceptedLedgerCache() override
perf::PerfLog & getPerfLog() override
TxQ & getTxQ() override
boost::asio::signal_set m_signals
std::unique_ptr< LedgerReplayer > m_ledgerReplayer
bool exists(std::string const &name) const
Returns true if a section with the given name exists.
The role of a ClosureCounter is to assist in shutdown by letting callers wait for the completion of c...
Provides the beast::insight::Collector service.
bool standalone() const
Definition Config.h:312
bool FORCE_MULTI_THREAD
Definition Config.h:220
std::size_t NODE_SIZE
Definition Config.h:194
int IO_WORKERS
Definition Config.h:216
int WORKERS
Definition Config.h:215
Routing table for objects identified by hash.
Definition HashRouter.h:77
Manages the lifetime of inbound ledgers.
Manages the acquisition and lifetime of transaction sets.
A pool of threads to perform work.
Definition JobQueue.h:37
Check the ledger/transaction databases to make sure they have continuity.
Manages the lifetime of ledger replay tasks.
Manages the current fee schedule.
Manages load sources.
Definition LoadManager.h:26
Manages partitions for logging.
Definition Log.h:32
Remembers manifests with the highest sequence number.
Definition Manifest.h:224
Provides server functionality for clients.
Definition NetworkOPs.h:69
A NodeStore::Scheduler which uses the JobQueue.
Persistency layer for NodeObject.
Definition Database.h:31
Simple NodeStore Scheduler that just performs the tasks synchronously.
static Manager & instance()
Returns the instance of the manager singleton.
virtual std::unique_ptr< Database > make_Database(std::size_t burstSize, Scheduler &scheduler, int readThreads, Section const &backendParameters, beast::Journal journal)=0
Construct a NodeStore database.
Represents the open ledger.
Definition OpenLedger.h:32
Writable ledger view that accumulates state and tx changes.
Definition OpenView.h:45
void rawTxInsert(key_type const &key, std::shared_ptr< Serializer const > const &txn, std::shared_ptr< Serializer const > const &metaData) override
Add a transaction to the tx map.
Definition OpenView.cpp:229
Manages the set of connected peers.
Definition Overlay.h:29
Keeps track of which ledgers haven't been fully saved.
A public key.
Definition PublicKey.h:42
static std::unique_ptr< RelationalDatabase > init(ServiceRegistry &registry, Config const &config, JobQueue &jobQueue)
init Creates and returns an appropriate RelationalDatabase instance based on configuration.
static std::unique_ptr< ResolverAsio > New(boost::asio::io_context &, beast::Journal)
A consumption charge.
Definition Charge.h:10
An endpoint that consumes resources.
Definition Consumer.h:16
Tracks load and resource consumption.
class to create database, launch online delete thread, and related SQLite database
Definition SHAMapStore.h:18
Holds the serialized result of parsing an input JSON object.
std::optional< STObject > object
The STObject if the parse was successful.
Holds a collection of configuration values.
Definition BasicConfig.h:24
std::size_t size() const
Returns the number of items in the container.
Manages various times used by the server.
Definition TimeKeeper.h:12
Transaction Queue.
Definition TxQ.h:41
Validator keys and manifest as set in configuration file.
std::optional< Keys > keys
constexpr bool parseHex(std::string_view sv)
Parse a hex string into a base_uint.
Definition base_uint.h:471
bool isZero() const
Definition base_uint.h:508
Singleton class that maintains performance counters and optionally writes Json-formatted data to a di...
Definition PerfLog.h:31
T empty(T... args)
T endl(T... args)
T hardware_concurrency(T... args)
T is_same_v
T load(T... args)
T make_unique(T... args)
T max(T... args)
T min(T... args)
unsigned int UInt
A namespace for easy access to logging severity values.
Definition Journal.h:10
bool lexicalCastChecked(Out &out, In in)
Intelligently convert from one type to another.
STL namespace.
std::unique_ptr< Manager > make_Manager(beast::insight::Collector::ptr const &collector, beast::Journal journal)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
std::shared_ptr< Ledger > loadByHash(uint256 const &ledgerHash, Application &app, bool acquire)
Definition Ledger.cpp:1037
void LogicError(std::string const &how) noexcept
Called when faulty logic causes a broken invariant.
csprng_engine & crypto_prng()
The default cryptographically secure PRNG.
std::unique_ptr< LedgerCleaner > make_LedgerCleaner(Application &app, beast::Journal journal)
std::unique_ptr< LoadManager > make_LoadManager(Application &app, beast::Journal journal)
Stopwatch & stopwatch()
Returns an instance of a wall clock.
Definition chrono.h:93
TxQ::Setup setup_TxQ(Config const &config)
Build a TxQ::Setup object from application configuration.
Definition TxQ.cpp:1733
std::tuple< std::shared_ptr< Ledger >, std::uint32_t, uint256 > getLatestLedger(Application &app)
Definition Ledger.cpp:1016
create_genesis_t const create_genesis
Definition Ledger.cpp:31
std::pair< PublicKey, SecretKey > getNodeIdentity(soci::session &session)
Returns a stable public and private key for this node.
Definition Wallet.cpp:102
std::unique_ptr< NetworkOPs > make_NetworkOPs(Application &app, NetworkOPs::clock_type &clock, bool standalone, std::size_t minPeerCount, bool startvalid, JobQueue &job_queue, LedgerMaster &ledgerMaster, ValidatorKeys const &validatorKeys, boost::asio::io_context &io_svc, beast::Journal journal, beast::insight::Collector::ptr const &collector)
std::enable_if_t< std::is_integral< Integral >::value, Integral > rand_int()
std::unique_ptr< SHAMapStore > make_SHAMapStore(Application &app, NodeStore::Scheduler &scheduler, beast::Journal journal)
std::unique_ptr< AmendmentTable > make_AmendmentTable(Application &app, std::chrono::seconds majorityTime, std::vector< AmendmentTable::FeatureInfo > const &supported, Section const &enabled, Section const &vetoed, beast::Journal journal)
std::unique_ptr< PeerSet > make_DummyPeerSet(Application &app)
Make a dummy PeerSet that does not do anything.
Definition PeerSet.cpp:164
@ hotACCOUNT_NODE
Definition NodeObject.h:15
std::unique_ptr< Application > make_Application(std::unique_ptr< Config > config, std::unique_ptr< Logs > logs, std::unique_ptr< TimeKeeper > timeKeeper)
ServerHandler::Setup setup_ServerHandler(Config const &config, std::ostream &&log)
std::shared_ptr< Ledger > loadByIndex(std::uint32_t ledgerIndex, Application &app, bool acquire)
Definition Ledger.cpp:1025
std::unique_ptr< CollectorManager > make_CollectorManager(Section const &params, beast::Journal journal)
std::unique_ptr< InboundLedgers > make_InboundLedgers(Application &app, InboundLedgers::clock_type &clock, beast::insight::Collector::ptr const &collector)
std::unique_ptr< ServerHandler > make_ServerHandler(Application &app, boost::asio::io_context &io_context, JobQueue &jobQueue, NetworkOPs &networkOPs, Resource::Manager &resourceManager, CollectorManager &cm)
constexpr auto megabytes(T value) noexcept
static void fixConfigPorts(Config &config, Endpoints const &endpoints)
DatabaseCon::Setup setup_DatabaseCon(Config const &c, std::optional< beast::Journal > j=std::nullopt)
Definition Config.cpp:1043
std::unique_ptr< DatabaseCon > makeWalletDB(DatabaseCon::Setup const &setup, beast::Journal j)
makeWalletDB Opens the wallet database and returns it.
Definition Wallet.cpp:9
void initAccountIdCache(std::size_t count)
Initialize the global cache used to map AccountID to base58 conversions.
Definition AccountID.cpp:85
std::unique_ptr< InboundTransactions > make_InboundTransactions(Application &app, beast::insight::Collector::ptr const &collector, std::function< void(std::shared_ptr< SHAMap > const &, bool)> gotSet)
void addJson(Json::Value &json, LedgerFill const &fill)
Given a Ledger and options, fill a Json::Value with a description of the ledger.
Overlay::Setup setup_Overlay(BasicConfig const &config)
HashRouter::Setup setup_HashRouter(Config const &config)
void forceValidity(HashRouter &router, uint256 const &txid, Validity validity)
Sets the validity of a given transaction in the cache.
Definition apply.cpp:89
std::unordered_map< std::string, boost::asio::ip::tcp::endpoint > Endpoints
Definition ServerImpl.h:20
std::optional< uint256 > getRegisteredFeature(std::string const &name)
Definition Feature.cpp:336
std::unique_ptr< PeerSetBuilder > make_PeerSetBuilder(Application &app)
Definition PeerSet.cpp:121
std::unique_ptr< Overlay > make_Overlay(Application &app, Overlay::Setup const &setup, ServerHandler &serverHandler, Resource::Manager &resourceManager, Resolver &resolver, boost::asio::io_context &io_context, BasicConfig const &config, beast::insight::Collector::ptr const &collector)
Creates the implementation of Overlay.
T ref(T... args)
T length(T... args)
T strlen(T... args)
static std::string importNodeDatabase()
T to_string(T... args)
T what(T... args)