#ifndef RIPPLE_APP_REPORTING_BACKENDINTERFACE_H_INCLUDED #define RIPPLE_APP_REPORTING_BACKENDINTERFACE_H_INCLUDED #include #include #include #include #include #include #include namespace Backend { class DatabaseTimeout : public std::exception { public: const char* what() const throw() override { return "Database read timed out. Please retry the request"; } }; template auto retryOnTimeout(F func, size_t waitMs = 500) { while (true) { try { return func(); } catch (DatabaseTimeout& t) { BOOST_LOG_TRIVIAL(error) << __func__ << " Database request timed out. Sleeping and retrying ... "; std::this_thread::sleep_for(std::chrono::milliseconds(waitMs)); } } } template auto synchronous(F&& f) { boost::asio::io_context ctx; boost::asio::io_context::strand strand(ctx); std::optional work; work.emplace(ctx); using R = typename std::result_of::type; if constexpr (!std::is_same::value) { R res; boost::asio::spawn( strand, [&f, &work, &res](boost::asio::yield_context yield) { res = f(yield); work.reset(); }); ctx.run(); return res; } else { boost::asio::spawn( strand, [&f, &work](boost::asio::yield_context yield) { f(yield); work.reset(); }); ctx.run(); } } template auto synchronousAndRetryOnTimeout(F&& f) { return retryOnTimeout([&]() { return synchronous(f); }); } class BackendInterface { protected: mutable std::shared_mutex rngMtx_; std::optional range; SimpleCache cache_; public: BackendInterface(boost::json::object const& config) { } virtual ~BackendInterface() { } // *** public read methods *** // All of these reads methods can throw DatabaseTimeout. When writing code // in an RPC handler, this exception does not need to be caught: when an RPC // results in a timeout, an error is returned to the client public: // *** ledger methods // SimpleCache const& cache() const { return cache_; } SimpleCache& cache() { return cache_; } virtual std::optional fetchLedgerBySequence( std::uint32_t const sequence, boost::asio::yield_context& yield) const = 0; virtual std::optional fetchLedgerByHash( ripple::uint256 const& hash, boost::asio::yield_context& yield) const = 0; virtual std::optional fetchLatestLedgerSequence(boost::asio::yield_context& yield) const = 0; std::optional fetchLedgerRange() const { std::shared_lock lck(rngMtx_); return range; } void updateRange(uint32_t newMax) { std::unique_lock lck(rngMtx_); assert(!range || newMax >= range->maxSequence); if (!range) range = {newMax, newMax}; else range->maxSequence = newMax; } std::optional fetchFees(std::uint32_t const seq, boost::asio::yield_context& yield) const; // *** transaction methods virtual std::optional fetchTransaction( ripple::uint256 const& hash, boost::asio::yield_context& yield) const = 0; virtual std::vector fetchTransactions( std::vector const& hashes, boost::asio::yield_context& yield) const = 0; virtual TransactionsAndCursor fetchAccountTransactions( ripple::AccountID const& account, std::uint32_t const limit, bool forward, std::optional const& cursor, boost::asio::yield_context& yield) const = 0; virtual std::vector fetchAllTransactionsInLedger( std::uint32_t const ledgerSequence, boost::asio::yield_context& yield) const = 0; virtual std::vector fetchAllTransactionHashesInLedger( std::uint32_t const ledgerSequence, boost::asio::yield_context& yield) const = 0; // *** NFT methods virtual std::optional fetchNFT( ripple::uint256 const& tokenID, std::uint32_t const ledgerSequence, boost::asio::yield_context& yield) const = 0; virtual TransactionsAndCursor fetchNFTTransactions( ripple::uint256 const& tokenID, std::uint32_t const limit, bool const forward, std::optional const& cursorIn, boost::asio::yield_context& yield) const = 0; // *** state data methods std::optional fetchLedgerObject( ripple::uint256 const& key, std::uint32_t const sequence, boost::asio::yield_context& yield) const; std::vector fetchLedgerObjects( std::vector const& keys, std::uint32_t const sequence, boost::asio::yield_context& yield) const; virtual std::optional doFetchLedgerObject( ripple::uint256 const& key, std::uint32_t const sequence, boost::asio::yield_context& yield) const = 0; virtual std::vector doFetchLedgerObjects( std::vector const& keys, std::uint32_t const sequence, boost::asio::yield_context& yield) const = 0; virtual std::vector fetchLedgerDiff( std::uint32_t const ledgerSequence, boost::asio::yield_context& yield) const = 0; // Fetches a page of ledger objects, ordered by key/index. // Used by ledger_data LedgerPage fetchLedgerPage( std::optional const& cursor, std::uint32_t const ledgerSequence, std::uint32_t const limit, bool outOfOrder, boost::asio::yield_context& yield) const; // Fetches the successor to key/index std::optional fetchSuccessorObject( ripple::uint256 key, std::uint32_t const ledgerSequence, boost::asio::yield_context& yield) const; std::optional fetchSuccessorKey( ripple::uint256 key, std::uint32_t const ledgerSequence, boost::asio::yield_context& yield) const; // Fetches the successor to key/index virtual std::optional doFetchSuccessorKey( ripple::uint256 key, std::uint32_t const ledgerSequence, boost::asio::yield_context& yield) const = 0; BookOffersPage fetchBookOffers( ripple::uint256 const& book, std::uint32_t const ledgerSequence, std::uint32_t const limit, std::optional const& cursor, boost::asio::yield_context& yield) const; std::optional hardFetchLedgerRange() const { return synchronous([&](boost::asio::yield_context yield) { return hardFetchLedgerRange(yield); }); } virtual std::optional hardFetchLedgerRange(boost::asio::yield_context& yield) const = 0; // Doesn't throw DatabaseTimeout. Should be used with care. std::optional hardFetchLedgerRangeNoThrow() const; // Doesn't throw DatabaseTimeout. Should be used with care. std::optional hardFetchLedgerRangeNoThrow(boost::asio::yield_context& yield) const; virtual void writeLedger( ripple::LedgerInfo const& ledgerInfo, std::string&& ledgerHeader) = 0; virtual void writeLedgerObject( std::string&& key, std::uint32_t const seq, std::string&& blob); virtual void writeTransaction( std::string&& hash, std::uint32_t const seq, std::uint32_t const date, std::string&& transaction, std::string&& metadata) = 0; virtual void writeNFTs(std::vector&& data) = 0; virtual void writeAccountTransactions(std::vector&& data) = 0; virtual void writeNFTTransactions(std::vector&& data) = 0; virtual void writeSuccessor( std::string&& key, std::uint32_t const seq, std::string&& successor) = 0; // Tell the database we are about to begin writing data for a particular // ledger. virtual void startWrites() const = 0; // Tell the database we have finished writing all data for a particular // ledger // TODO change the return value to represent different results. committed, // write conflict, errored, successful but not committed bool finishWrites(std::uint32_t const ledgerSequence); virtual bool doOnlineDelete( std::uint32_t numLedgersToKeep, boost::asio::yield_context& yield) const = 0; // Open the database. Set up all of the necessary objects and // datastructures. After this call completes, the database is ready for // use. virtual void open(bool readOnly) = 0; // Close the database, releasing any resources virtual void close(){}; virtual bool isTooBusy() const = 0; // *** private helper methods private: virtual void doWriteLedgerObject( std::string&& key, std::uint32_t const seq, std::string&& blob) = 0; virtual bool doFinishWrites() = 0; }; } // namespace Backend using BackendInterface = Backend::BackendInterface; #endif