mirror of
				https://github.com/XRPLF/clio.git
				synced 2025-11-04 11:55:51 +00:00 
			
		
		
		
	@@ -1,6 +1,7 @@
 | 
			
		||||
add_subdirectory(util)
 | 
			
		||||
add_subdirectory(data)
 | 
			
		||||
add_subdirectory(etl)
 | 
			
		||||
add_subdirectory(etlng)
 | 
			
		||||
add_subdirectory(feed)
 | 
			
		||||
add_subdirectory(rpc)
 | 
			
		||||
add_subdirectory(web)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
add_library(clio_app)
 | 
			
		||||
target_sources(clio_app PRIVATE CliArgs.cpp ClioApplication.cpp)
 | 
			
		||||
 | 
			
		||||
target_link_libraries(clio_app PUBLIC clio_etl clio_feed clio_web clio_rpc)
 | 
			
		||||
target_link_libraries(clio_app PUBLIC clio_etl clio_etlng clio_feed clio_web clio_rpc)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										5
									
								
								src/etlng/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/etlng/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
add_library(clio_etlng INTERFACE)
 | 
			
		||||
 | 
			
		||||
# target_sources(clio_etlng PRIVATE )
 | 
			
		||||
 | 
			
		||||
target_link_libraries(clio_etlng INTERFACE clio_data)
 | 
			
		||||
							
								
								
									
										129
									
								
								src/etlng/Models.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								src/etlng/Models.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,129 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, the clio developers.
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and distribute this software for any
 | 
			
		||||
    purpose with or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL,  DIRECT,  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "util/Concepts.hpp"
 | 
			
		||||
 | 
			
		||||
#include <boost/json/object.hpp>
 | 
			
		||||
#include <fmt/core.h>
 | 
			
		||||
#include <xrpl/basics/Blob.h>
 | 
			
		||||
#include <xrpl/basics/base_uint.h>
 | 
			
		||||
#include <xrpl/proto/org/xrpl/rpc/v1/get_ledger.pb.h>
 | 
			
		||||
#include <xrpl/proto/org/xrpl/rpc/v1/ledger.pb.h>
 | 
			
		||||
#include <xrpl/protocol/LedgerHeader.h>
 | 
			
		||||
#include <xrpl/protocol/STTx.h>
 | 
			
		||||
#include <xrpl/protocol/TxFormats.h>
 | 
			
		||||
#include <xrpl/protocol/TxMeta.h>
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace etlng::model {
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief A specification for the Registry.
 | 
			
		||||
 *
 | 
			
		||||
 * This specification simply defines the transaction types that are to be filtered out from the incoming transactions by
 | 
			
		||||
 * the Registry for its `onTransaction` and `onInitialTransaction` hooks.
 | 
			
		||||
 * It's a compilation error to list the same transaction type more than once.
 | 
			
		||||
 */
 | 
			
		||||
template <ripple::TxType... Types>
 | 
			
		||||
    requires(util::hasNoDuplicates(Types...))
 | 
			
		||||
struct Spec {
 | 
			
		||||
    static constexpr bool SpecTag = true;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Checks if the transaction type was requested.
 | 
			
		||||
     *
 | 
			
		||||
     * @param type The transaction type
 | 
			
		||||
     * @return true if the transaction was requested; false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] constexpr static bool
 | 
			
		||||
    wants(ripple::TxType type) noexcept
 | 
			
		||||
    {
 | 
			
		||||
        return ((Types == type) || ...);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Represents a single transaction on the ledger.
 | 
			
		||||
 */
 | 
			
		||||
struct Transaction {
 | 
			
		||||
    std::string raw;  // raw binary blob
 | 
			
		||||
    std::string metaRaw;
 | 
			
		||||
 | 
			
		||||
    // unpacked blob and meta
 | 
			
		||||
    ripple::STTx sttx;
 | 
			
		||||
    ripple::TxMeta meta;
 | 
			
		||||
 | 
			
		||||
    // commonly used stuff
 | 
			
		||||
    ripple::uint256 id;
 | 
			
		||||
    std::string key;  // key is the above id as a string of 32 characters
 | 
			
		||||
    ripple::TxType type;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Represents a single object on the ledger.
 | 
			
		||||
 */
 | 
			
		||||
struct Object {
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Modification type for the object.
 | 
			
		||||
     */
 | 
			
		||||
    enum class ModType : int {
 | 
			
		||||
        Unspecified = 0,
 | 
			
		||||
        Created = 1,
 | 
			
		||||
        Modified = 2,
 | 
			
		||||
        Deleted = 3,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    ripple::uint256 key;
 | 
			
		||||
    std::string keyRaw;
 | 
			
		||||
    ripple::Blob data;
 | 
			
		||||
    std::string dataRaw;
 | 
			
		||||
    std::string successor;
 | 
			
		||||
    std::string predecessor;
 | 
			
		||||
 | 
			
		||||
    ModType type;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Represents a book successor.
 | 
			
		||||
 */
 | 
			
		||||
struct BookSuccessor {
 | 
			
		||||
    std::string firstBook;
 | 
			
		||||
    std::string bookBase;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Represents an entire ledger diff worth of transactions and objects.
 | 
			
		||||
 */
 | 
			
		||||
struct LedgerData {
 | 
			
		||||
    std::vector<Transaction> transactions;
 | 
			
		||||
    std::vector<Object> objects;
 | 
			
		||||
    std::optional<std::vector<BookSuccessor>> successors;
 | 
			
		||||
 | 
			
		||||
    ripple::LedgerHeader header;
 | 
			
		||||
    std::string rawHeader;
 | 
			
		||||
    uint32_t seq;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace etlng::model
 | 
			
		||||
							
								
								
									
										108
									
								
								src/etlng/RegistryInterface.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								src/etlng/RegistryInterface.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,108 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, the clio developers.
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and distribute this software for any
 | 
			
		||||
    purpose with or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL,  DIRECT,  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "etlng/Models.hpp"
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace etlng {
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief The interface for a registry that can dispatch transactions and objects to extensions.
 | 
			
		||||
 *
 | 
			
		||||
 * This class defines the interface for dispatching data through to extensions.
 | 
			
		||||
 *
 | 
			
		||||
 * @note
 | 
			
		||||
 * The registry itself consists of Extensions.
 | 
			
		||||
 * Each extension must define at least one valid hook:
 | 
			
		||||
 * - for ongoing ETL dispatch:
 | 
			
		||||
 *   - void onLedgerData(etlng::model::LedgerData const&)
 | 
			
		||||
 *   - void onTransaction(uint32_t, etlng::model::Transaction const&)
 | 
			
		||||
 *   - void onObject(uint32_t, etlng::model::Object const&)
 | 
			
		||||
 * - for initial ledger load
 | 
			
		||||
 *   - void onInitialData(etlng::model::LedgerData const&)
 | 
			
		||||
 *   - void onInitialTransaction(uint32_t, etlng::model::Transaction const&)
 | 
			
		||||
 * - for initial objects (called for each downloaded batch)
 | 
			
		||||
 *   - void onInitialObjects(uint32_t, std::vector<etlng::model::Object> const&, std::string)
 | 
			
		||||
 *   - void onInitialObject(uint32_t, etlng::model::Object const&)
 | 
			
		||||
 *
 | 
			
		||||
 * When the registry dispatches (initial)data or objects, each of the above hooks will be called in order on each
 | 
			
		||||
 * registered extension.
 | 
			
		||||
 * This means that the order of execution is from left to right (hooks) and top to bottom (registered extensions).
 | 
			
		||||
 *
 | 
			
		||||
 * If either `onTransaction` or `onInitialTransaction` are defined, the extension will have to additionally define a
 | 
			
		||||
 * Specification. The specification lists transaction types to filter from the incoming data such that `onTransaction`
 | 
			
		||||
 * and `onInitialTransaction` are only called for the transactions that are of interest for the given extension.
 | 
			
		||||
 *
 | 
			
		||||
 * The specification is setup like so:
 | 
			
		||||
 * @code{.cpp}
 | 
			
		||||
 * struct Ext {
 | 
			
		||||
 *   using spec = etlng::model::Spec<
 | 
			
		||||
 *     ripple::TxType::ttNFTOKEN_BURN,
 | 
			
		||||
 *     ripple::TxType::ttNFTOKEN_ACCEPT_OFFER,
 | 
			
		||||
 *     ripple::TxType::ttNFTOKEN_CREATE_OFFER,
 | 
			
		||||
 *     ripple::TxType::ttNFTOKEN_CANCEL_OFFER,
 | 
			
		||||
 *     ripple::TxType::ttNFTOKEN_MINT>;
 | 
			
		||||
 *
 | 
			
		||||
 *   static void
 | 
			
		||||
 *   onInitialTransaction(uint32_t, etlng::model::Transaction const&);
 | 
			
		||||
 * };
 | 
			
		||||
 * @endcode
 | 
			
		||||
 */
 | 
			
		||||
struct RegistryInterface {
 | 
			
		||||
    virtual ~RegistryInterface() = default;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Dispatch initial objects.
 | 
			
		||||
     *
 | 
			
		||||
     * These objects are received during initial ledger load.
 | 
			
		||||
     *
 | 
			
		||||
     * @param seq The sequence
 | 
			
		||||
     * @param data The objects to dispatch
 | 
			
		||||
     * @param lastKey The predcessor of the first object in data if known; an empty string otherwise
 | 
			
		||||
     */
 | 
			
		||||
    virtual void
 | 
			
		||||
    dispatchInitialObjects(uint32_t seq, std::vector<model::Object> const& data, std::string lastKey) = 0;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Dispatch initial ledger data.
 | 
			
		||||
     *
 | 
			
		||||
     * The transactions, header and edge keys are received during initial ledger load.
 | 
			
		||||
     *
 | 
			
		||||
     * @param data The data to dispatch
 | 
			
		||||
     */
 | 
			
		||||
    virtual void
 | 
			
		||||
    dispatchInitialData(model::LedgerData const& data) = 0;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Dispatch an entire ledger diff.
 | 
			
		||||
     *
 | 
			
		||||
     * This is used to dispatch incoming diffs through the extensions.
 | 
			
		||||
     *
 | 
			
		||||
     * @param data The data to dispatch
 | 
			
		||||
     */
 | 
			
		||||
    virtual void
 | 
			
		||||
    dispatch(model::LedgerData const& data) = 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace etlng
 | 
			
		||||
							
								
								
									
										219
									
								
								src/etlng/impl/Registry.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										219
									
								
								src/etlng/impl/Registry.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,219 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, the clio developers.
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and distribute this software for any
 | 
			
		||||
    purpose with or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL,  DIRECT,  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "etlng/Models.hpp"
 | 
			
		||||
#include "etlng/RegistryInterface.hpp"
 | 
			
		||||
 | 
			
		||||
#include <xrpl/protocol/TxFormats.h>
 | 
			
		||||
 | 
			
		||||
#include <concepts>
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <tuple>
 | 
			
		||||
#include <type_traits>
 | 
			
		||||
#include <utility>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace etlng::impl {
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
concept HasLedgerDataHook = requires(T p) {
 | 
			
		||||
    { p.onLedgerData(std::declval<etlng::model::LedgerData>()) } -> std::same_as<void>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
concept HasInitialDataHook = requires(T p) {
 | 
			
		||||
    { p.onInitialData(std::declval<etlng::model::LedgerData>()) } -> std::same_as<void>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
concept HasTransactionHook = requires(T p) {
 | 
			
		||||
    { p.onTransaction(uint32_t{}, std::declval<etlng::model::Transaction>()) } -> std::same_as<void>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
concept HasObjectHook = requires(T p) {
 | 
			
		||||
    { p.onObject(uint32_t{}, std::declval<etlng::model::Object>()) } -> std::same_as<void>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
concept HasInitialTransactionHook = requires(T p) {
 | 
			
		||||
    { p.onInitialTransaction(uint32_t{}, std::declval<etlng::model::Transaction>()) } -> std::same_as<void>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
concept HasInitialObjectsHook = requires(T p) {
 | 
			
		||||
    {
 | 
			
		||||
        p.onInitialObjects(uint32_t{}, std::declval<std::vector<etlng::model::Object>>(), std::string{})
 | 
			
		||||
    } -> std::same_as<void>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
concept HasInitialObjectHook = requires(T p) {
 | 
			
		||||
    { p.onInitialObject(uint32_t{}, std::declval<etlng::model::Object>()) } -> std::same_as<void>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
concept ContainsSpec = std::decay_t<T>::spec::SpecTag;
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
concept ContainsValidHook = HasLedgerDataHook<T> or HasInitialDataHook<T> or
 | 
			
		||||
    (HasTransactionHook<T> and ContainsSpec<T>) or (HasInitialTransactionHook<T> and ContainsSpec<T>) or
 | 
			
		||||
    HasObjectHook<T> or HasInitialObjectsHook<T> or HasInitialObjectHook<T>;
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
concept NoTwoOfKind = not(HasLedgerDataHook<T> and HasTransactionHook<T>) and
 | 
			
		||||
    not(HasInitialDataHook<T> and HasInitialTransactionHook<T>) and not(HasInitialDataHook<T> and HasObjectHook<T>) and
 | 
			
		||||
    not(HasInitialObjectsHook<T> and HasInitialObjectHook<T>);
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
concept SomeExtension = NoTwoOfKind<T> and ContainsValidHook<T>;
 | 
			
		||||
 | 
			
		||||
template <SomeExtension... Ps>
 | 
			
		||||
class Registry : public RegistryInterface {
 | 
			
		||||
    std::tuple<Ps...> store_;
 | 
			
		||||
 | 
			
		||||
    static_assert(
 | 
			
		||||
        (((not HasTransactionHook<std::decay_t<Ps>>) or ContainsSpec<std::decay_t<Ps>>) and ...),
 | 
			
		||||
        "Spec must be specified when 'onTransaction' function exists."
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    static_assert(
 | 
			
		||||
        (((not HasInitialTransactionHook<std::decay_t<Ps>>) or ContainsSpec<std::decay_t<Ps>>) and ...),
 | 
			
		||||
        "Spec must be specified when 'onInitialTransaction' function exists."
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    explicit constexpr Registry(SomeExtension auto&&... exts)
 | 
			
		||||
        requires(std::is_same_v<std::decay_t<decltype(exts)>, std::decay_t<Ps>> and ...)
 | 
			
		||||
        : store_(std::forward<Ps>(exts)...)
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ~Registry() override = default;
 | 
			
		||||
    Registry(Registry const&) = delete;
 | 
			
		||||
    Registry(Registry&&) = default;
 | 
			
		||||
    Registry&
 | 
			
		||||
    operator=(Registry const&) = delete;
 | 
			
		||||
    Registry&
 | 
			
		||||
    operator=(Registry&&) = default;
 | 
			
		||||
 | 
			
		||||
    constexpr void
 | 
			
		||||
    dispatch(model::LedgerData const& data) override
 | 
			
		||||
    {
 | 
			
		||||
        // send entire batch of data at once
 | 
			
		||||
        {
 | 
			
		||||
            auto const expand = [&](auto& p) {
 | 
			
		||||
                if constexpr (requires { p.onLedgerData(data); }) {
 | 
			
		||||
                    p.onLedgerData(data);
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            std::apply([&expand](auto&&... xs) { (expand(xs), ...); }, store_);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // send filtered transactions
 | 
			
		||||
        {
 | 
			
		||||
            auto const expand = [&]<typename P>(P& p, model::Transaction const& t) {
 | 
			
		||||
                if constexpr (requires { p.onTransaction(data.seq, t); }) {
 | 
			
		||||
                    if (std::decay_t<P>::spec::wants(t.type))
 | 
			
		||||
                        p.onTransaction(data.seq, t);
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            for (auto const& t : data.transactions) {
 | 
			
		||||
                std::apply([&expand, &t](auto&&... xs) { (expand(xs, t), ...); }, store_);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // send per object path
 | 
			
		||||
        {
 | 
			
		||||
            auto const expand = [&]<typename P>(P&& p, model::Object const& o) {
 | 
			
		||||
                if constexpr (requires { p.onObject(data.seq, o); }) {
 | 
			
		||||
                    p.onObject(data.seq, o);
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            for (auto const& obj : data.objects) {
 | 
			
		||||
                std::apply([&expand, &obj](auto&&... xs) { (expand(xs, obj), ...); }, store_);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    constexpr void
 | 
			
		||||
    dispatchInitialObjects(uint32_t seq, std::vector<model::Object> const& data, std::string lastKey) override
 | 
			
		||||
    {
 | 
			
		||||
        // send entire vector path
 | 
			
		||||
        {
 | 
			
		||||
            auto const expand = [&](auto&& p) {
 | 
			
		||||
                if constexpr (requires { p.onInitialObjects(seq, data, lastKey); }) {
 | 
			
		||||
                    p.onInitialObjects(seq, data, lastKey);
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            std::apply([&expand](auto&&... xs) { (expand(xs), ...); }, store_);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // send per object path
 | 
			
		||||
        {
 | 
			
		||||
            auto const expand = [&]<typename P>(P&& p, model::Object const& o) {
 | 
			
		||||
                if constexpr (requires { p.onInitialObject(seq, o); }) {
 | 
			
		||||
                    p.onInitialObject(seq, o);
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            for (auto const& obj : data) {
 | 
			
		||||
                std::apply([&expand, &obj](auto&&... xs) { (expand(xs, obj), ...); }, store_);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    constexpr void
 | 
			
		||||
    dispatchInitialData(model::LedgerData const& data) override
 | 
			
		||||
    {
 | 
			
		||||
        // send entire batch path
 | 
			
		||||
        {
 | 
			
		||||
            auto const expand = [&](auto&& p) {
 | 
			
		||||
                if constexpr (requires { p.onInitialData(data); }) {
 | 
			
		||||
                    p.onInitialData(data);
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            std::apply([&expand](auto&&... xs) { (expand(xs), ...); }, store_);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // send per tx path
 | 
			
		||||
        {
 | 
			
		||||
            auto const expand = [&]<typename P>(P&& p, model::Transaction const& tx) {
 | 
			
		||||
                if constexpr (requires { p.onInitialTransaction(data.seq, tx); }) {
 | 
			
		||||
                    if (std::decay_t<P>::spec::wants(tx.type))
 | 
			
		||||
                        p.onInitialTransaction(data.seq, tx);
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            for (auto const& tx : data.transactions) {
 | 
			
		||||
                std::apply([&expand, &tx](auto&&... xs) { (expand(xs, tx), ...); }, store_);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace etlng::impl
 | 
			
		||||
@@ -19,6 +19,8 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <array>
 | 
			
		||||
#include <type_traits>
 | 
			
		||||
 | 
			
		||||
namespace util {
 | 
			
		||||
@@ -29,4 +31,19 @@ namespace util {
 | 
			
		||||
template <typename T>
 | 
			
		||||
concept SomeNumberType = std::is_arithmetic_v<T> && !std::is_same_v<T, bool> && !std::is_const_v<T>;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Checks that the list of given values contains no duplicates
 | 
			
		||||
 *
 | 
			
		||||
 * @param values The list of values to check
 | 
			
		||||
 * @returns true if no duplicates exist; false otherwise
 | 
			
		||||
 */
 | 
			
		||||
static consteval auto
 | 
			
		||||
hasNoDuplicates(auto&&... values)
 | 
			
		||||
{
 | 
			
		||||
    auto store = std::array{values...};
 | 
			
		||||
    auto end = store.end();
 | 
			
		||||
    std::ranges::sort(store);
 | 
			
		||||
    return (std::unique(std::begin(store), end) == end);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace util
 | 
			
		||||
 
 | 
			
		||||
@@ -31,6 +31,8 @@ target_sources(
 | 
			
		||||
          etl/SourceImplTests.cpp
 | 
			
		||||
          etl/SubscriptionSourceTests.cpp
 | 
			
		||||
          etl/TransformerTests.cpp
 | 
			
		||||
          # ETLng
 | 
			
		||||
          etlng/RegistryTests.cpp
 | 
			
		||||
          # Feed
 | 
			
		||||
          feed/BookChangesFeedTests.cpp
 | 
			
		||||
          feed/ForwardFeedTests.cpp
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										530
									
								
								tests/unit/etlng/RegistryTests.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										530
									
								
								tests/unit/etlng/RegistryTests.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,530 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, the clio developers.
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and distribute this software for any
 | 
			
		||||
    purpose with or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL,  DIRECT,  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#include "etlng/Models.hpp"
 | 
			
		||||
#include "etlng/impl/Registry.hpp"
 | 
			
		||||
#include "util/LoggerFixtures.hpp"
 | 
			
		||||
#include "util/StringUtils.hpp"
 | 
			
		||||
#include "util/TestObject.hpp"
 | 
			
		||||
 | 
			
		||||
#include <gmock/gmock.h>
 | 
			
		||||
#include <gtest/gtest.h>
 | 
			
		||||
#include <xrpl/basics/base_uint.h>
 | 
			
		||||
#include <xrpl/protocol/STTx.h>
 | 
			
		||||
#include <xrpl/protocol/Serializer.h>
 | 
			
		||||
#include <xrpl/protocol/TxFormats.h>
 | 
			
		||||
#include <xrpl/protocol/TxMeta.h>
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <utility>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
using namespace etlng::impl;
 | 
			
		||||
 | 
			
		||||
namespace compiletime::checks {
 | 
			
		||||
 | 
			
		||||
struct Ext1 {
 | 
			
		||||
    static void
 | 
			
		||||
    onLedgerData(etlng::model::LedgerData const&);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct Ext2 {
 | 
			
		||||
    static void
 | 
			
		||||
    onInitialObjects(uint32_t, std::vector<etlng::model::Object> const&, std::string);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct Ext3 {
 | 
			
		||||
    static void
 | 
			
		||||
    onInitialData(etlng::model::LedgerData const&);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct Ext4SpecMissing {
 | 
			
		||||
    static void
 | 
			
		||||
    onTransaction(uint32_t, etlng::model::Transaction const&);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct Ext4Fixed {
 | 
			
		||||
    using spec = etlng::model::Spec<ripple::TxType::ttNFTOKEN_BURN>;
 | 
			
		||||
 | 
			
		||||
    static void
 | 
			
		||||
    onTransaction(uint32_t, etlng::model::Transaction const&);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct Ext5 {
 | 
			
		||||
    static void
 | 
			
		||||
    onInitialObject(uint32_t, etlng::model::Object const&);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct Ext6SpecMissing {
 | 
			
		||||
    static void
 | 
			
		||||
    onInitialTransaction(uint32_t, etlng::model::Transaction const&);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct Ext6Fixed {
 | 
			
		||||
    using spec = etlng::model::Spec<ripple::TxType::ttNFTOKEN_BURN>;
 | 
			
		||||
 | 
			
		||||
    static void
 | 
			
		||||
    onInitialTransaction(uint32_t, etlng::model::Transaction const&);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct ExtRealistic {
 | 
			
		||||
    using spec = etlng::model::Spec<
 | 
			
		||||
        ripple::TxType::ttNFTOKEN_BURN,
 | 
			
		||||
        ripple::TxType::ttNFTOKEN_ACCEPT_OFFER,
 | 
			
		||||
        ripple::TxType::ttNFTOKEN_CREATE_OFFER,
 | 
			
		||||
        ripple::TxType::ttNFTOKEN_CANCEL_OFFER,
 | 
			
		||||
        ripple::TxType::ttNFTOKEN_MINT>;
 | 
			
		||||
 | 
			
		||||
    static void
 | 
			
		||||
    onLedgerData(etlng::model::LedgerData const&);
 | 
			
		||||
    static void
 | 
			
		||||
    onInitialObject(uint32_t, etlng::model::Object const&);
 | 
			
		||||
    static void
 | 
			
		||||
    onInitialTransaction(uint32_t, etlng::model::Transaction const&);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct ExtCombinesTwoOfKind : Ext2, Ext5 {};
 | 
			
		||||
struct ExtEmpty {};
 | 
			
		||||
 | 
			
		||||
// check all expectations of an extension are met
 | 
			
		||||
static_assert(not SomeExtension<ExtEmpty>);
 | 
			
		||||
static_assert(SomeExtension<Ext1>);
 | 
			
		||||
static_assert(SomeExtension<Ext2>);
 | 
			
		||||
static_assert(SomeExtension<Ext3>);
 | 
			
		||||
static_assert(not SomeExtension<Ext4SpecMissing>);
 | 
			
		||||
static_assert(SomeExtension<Ext4Fixed>);
 | 
			
		||||
static_assert(SomeExtension<Ext5>);
 | 
			
		||||
static_assert(not SomeExtension<Ext6SpecMissing>);
 | 
			
		||||
static_assert(SomeExtension<Ext6Fixed>);
 | 
			
		||||
static_assert(SomeExtension<ExtRealistic>);
 | 
			
		||||
static_assert(not SomeExtension<ExtCombinesTwoOfKind>);
 | 
			
		||||
 | 
			
		||||
struct ValidSpec {
 | 
			
		||||
    using spec = etlng::model::Spec<ripple::ttNFTOKEN_BURN, ripple::ttNFTOKEN_MINT>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// invalid spec does not compile:
 | 
			
		||||
// struct DuplicatesSpec {
 | 
			
		||||
//     using spec = etlng::model::Spec<ripple::ttNFTOKEN_BURN, ripple::ttNFTOKEN_BURN, ripple::ttNFTOKEN_MINT>;
 | 
			
		||||
// };
 | 
			
		||||
 | 
			
		||||
static_assert(ContainsSpec<ValidSpec>);
 | 
			
		||||
 | 
			
		||||
}  // namespace compiletime::checks
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
 | 
			
		||||
auto constinit LEDGERHASH = "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652";
 | 
			
		||||
auto constinit SEQ = 30;
 | 
			
		||||
 | 
			
		||||
struct MockExtLedgerData {
 | 
			
		||||
    MOCK_METHOD(void, onLedgerData, (etlng::model::LedgerData const&), (const));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct MockExtInitialData {
 | 
			
		||||
    MOCK_METHOD(void, onInitialData, (etlng::model::LedgerData const&), (const));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct MockExtOnObject {
 | 
			
		||||
    MOCK_METHOD(void, onObject, (uint32_t, etlng::model::Object const&), (const));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct MockExtTransactionNftBurn {
 | 
			
		||||
    using spec = etlng::model::Spec<ripple::TxType::ttNFTOKEN_BURN>;
 | 
			
		||||
    MOCK_METHOD(void, onTransaction, (uint32_t, etlng::model::Transaction const&), (const));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct MockExtTransactionNftOffer {
 | 
			
		||||
    using spec = etlng::model::Spec<
 | 
			
		||||
        ripple::TxType::ttNFTOKEN_CREATE_OFFER,
 | 
			
		||||
        ripple::TxType::ttNFTOKEN_CANCEL_OFFER,
 | 
			
		||||
        ripple::TxType::ttNFTOKEN_ACCEPT_OFFER>;
 | 
			
		||||
    MOCK_METHOD(void, onTransaction, (uint32_t, etlng::model::Transaction const&), (const));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct MockExtInitialObject {
 | 
			
		||||
    MOCK_METHOD(void, onInitialObject, (uint32_t, etlng::model::Object const&), (const));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct MockExtInitialObjects {
 | 
			
		||||
    MOCK_METHOD(void, onInitialObjects, (uint32_t, std::vector<etlng::model::Object> const&, std::string), (const));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct MockExtNftBurn {
 | 
			
		||||
    using spec = etlng::model::Spec<ripple::TxType::ttNFTOKEN_BURN>;
 | 
			
		||||
    MOCK_METHOD(void, onInitialTransaction, (uint32_t, etlng::model::Transaction const&), (const));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct MockExtNftOffer {
 | 
			
		||||
    using spec = etlng::model::Spec<
 | 
			
		||||
        ripple::TxType::ttNFTOKEN_CREATE_OFFER,
 | 
			
		||||
        ripple::TxType::ttNFTOKEN_CANCEL_OFFER,
 | 
			
		||||
        ripple::TxType::ttNFTOKEN_ACCEPT_OFFER>;
 | 
			
		||||
    MOCK_METHOD(void, onInitialTransaction, (uint32_t, etlng::model::Transaction const&), (const));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct RegistryTest : NoLoggerFixture {
 | 
			
		||||
    static std::pair<ripple::STTx, ripple::TxMeta>
 | 
			
		||||
    CreateNftTxAndMeta()
 | 
			
		||||
    {
 | 
			
		||||
        ripple::uint256 hash;
 | 
			
		||||
        EXPECT_TRUE(hash.parseHex("6C7F69A6D25A13AC4A2E9145999F45D4674F939900017A96885FDC2757E9284E"));
 | 
			
		||||
 | 
			
		||||
        static constinit auto txnHex =
 | 
			
		||||
            "1200192200000008240011CC9B201B001F71D6202A0000000168400000"
 | 
			
		||||
            "000000000C7321ED475D1452031E8F9641AF1631519A58F7B8681E172E"
 | 
			
		||||
            "4838AA0E59408ADA1727DD74406960041F34F10E0CBB39444B4D4E577F"
 | 
			
		||||
            "C0B7E8D843D091C2917E96E7EE0E08B30C91413EC551A2B8A1D405E8BA"
 | 
			
		||||
            "34FE185D8B10C53B40928611F2DE3B746F0303751868747470733A2F2F"
 | 
			
		||||
            "677265677765697362726F642E636F6D81146203F49C21D5D6E022CB16"
 | 
			
		||||
            "DE3538F248662FC73C";
 | 
			
		||||
 | 
			
		||||
        static constinit auto txnMeta =
 | 
			
		||||
            "201C00000001F8E511005025001F71B3556ED9C9459001E4F4A9121F4E"
 | 
			
		||||
            "07AB6D14898A5BBEF13D85C25D743540DB59F3CF566203F49C21D5D6E0"
 | 
			
		||||
            "22CB16DE3538F248662FC73CFFFFFFFFFFFFFFFFFFFFFFFFE6FAEC5A00"
 | 
			
		||||
            "0800006203F49C21D5D6E022CB16DE3538F248662FC73C8962EFA00000"
 | 
			
		||||
            "0006751868747470733A2F2F677265677765697362726F642E636F6DE1"
 | 
			
		||||
            "EC5A000800006203F49C21D5D6E022CB16DE3538F248662FC73C93E8B1"
 | 
			
		||||
            "C200000028751868747470733A2F2F677265677765697362726F642E63"
 | 
			
		||||
            "6F6DE1EC5A000800006203F49C21D5D6E022CB16DE3538F248662FC73C"
 | 
			
		||||
            "9808B6B90000001D751868747470733A2F2F677265677765697362726F"
 | 
			
		||||
            "642E636F6DE1EC5A000800006203F49C21D5D6E022CB16DE3538F24866"
 | 
			
		||||
            "2FC73C9C28BBAC00000012751868747470733A2F2F6772656777656973"
 | 
			
		||||
            "62726F642E636F6DE1EC5A000800006203F49C21D5D6E022CB16DE3538"
 | 
			
		||||
            "F248662FC73CA048C0A300000007751868747470733A2F2F6772656777"
 | 
			
		||||
            "65697362726F642E636F6DE1EC5A000800006203F49C21D5D6E022CB16"
 | 
			
		||||
            "DE3538F248662FC73CAACE82C500000029751868747470733A2F2F6772"
 | 
			
		||||
            "65677765697362726F642E636F6DE1EC5A000800006203F49C21D5D6E0"
 | 
			
		||||
            "22CB16DE3538F248662FC73CAEEE87B80000001E751868747470733A2F"
 | 
			
		||||
            "2F677265677765697362726F642E636F6DE1EC5A000800006203F49C21"
 | 
			
		||||
            "D5D6E022CB16DE3538F248662FC73CB30E8CAF00000013751868747470"
 | 
			
		||||
            "733A2F2F677265677765697362726F642E636F6DE1EC5A000800006203"
 | 
			
		||||
            "F49C21D5D6E022CB16DE3538F248662FC73CB72E91A200000008751868"
 | 
			
		||||
            "747470733A2F2F677265677765697362726F642E636F6DE1EC5A000800"
 | 
			
		||||
            "006203F49C21D5D6E022CB16DE3538F248662FC73CC1B453C40000002A"
 | 
			
		||||
            "751868747470733A2F2F677265677765697362726F642E636F6DE1EC5A"
 | 
			
		||||
            "000800006203F49C21D5D6E022CB16DE3538F248662FC73CC5D458BB00"
 | 
			
		||||
            "00001F751868747470733A2F2F677265677765697362726F642E636F6D"
 | 
			
		||||
            "E1EC5A000800006203F49C21D5D6E022CB16DE3538F248662FC73CC9F4"
 | 
			
		||||
            "5DAE00000014751868747470733A2F2F677265677765697362726F642E"
 | 
			
		||||
            "636F6DE1EC5A000800006203F49C21D5D6E022CB16DE3538F248662FC7"
 | 
			
		||||
            "3CCE1462A500000009751868747470733A2F2F67726567776569736272"
 | 
			
		||||
            "6F642E636F6DE1EC5A000800006203F49C21D5D6E022CB16DE3538F248"
 | 
			
		||||
            "662FC73CD89A24C70000002B751868747470733A2F2F67726567776569"
 | 
			
		||||
            "7362726F642E636F6DE1EC5A000800006203F49C21D5D6E022CB16DE35"
 | 
			
		||||
            "38F248662FC73CDCBA29BA00000020751868747470733A2F2F67726567"
 | 
			
		||||
            "7765697362726F642E636F6DE1EC5A000800006203F49C21D5D6E022CB"
 | 
			
		||||
            "16DE3538F248662FC73CE0DA2EB100000015751868747470733A2F2F67"
 | 
			
		||||
            "7265677765697362726F642E636F6DE1EC5A000800006203F49C21D5D6"
 | 
			
		||||
            "E022CB16DE3538F248662FC73CE4FA33A40000000A751868747470733A"
 | 
			
		||||
            "2F2F677265677765697362726F642E636F6DE1EC5A000800006203F49C"
 | 
			
		||||
            "21D5D6E022CB16DE3538F248662FC73CF39FFABD000000217518687474"
 | 
			
		||||
            "70733A2F2F677265677765697362726F642E636F6DE1EC5A0008000062"
 | 
			
		||||
            "03F49C21D5D6E022CB16DE3538F248662FC73CF7BFFFB0000000167518"
 | 
			
		||||
            "68747470733A2F2F677265677765697362726F642E636F6DE1EC5A0008"
 | 
			
		||||
            "00006203F49C21D5D6E022CB16DE3538F248662FC73CFBE004A7000000"
 | 
			
		||||
            "0B751868747470733A2F2F677265677765697362726F642E636F6DE1F1"
 | 
			
		||||
            "E1E72200000000501A6203F49C21D5D6E022CB16DE3538F248662FC73C"
 | 
			
		||||
            "662FC73C8962EFA000000006FAEC5A000800006203F49C21D5D6E022CB"
 | 
			
		||||
            "16DE3538F248662FC73C8962EFA000000006751868747470733A2F2F67"
 | 
			
		||||
            "7265677765697362726F642E636F6DE1EC5A000800006203F49C21D5D6"
 | 
			
		||||
            "E022CB16DE3538F248662FC73C93E8B1C200000028751868747470733A"
 | 
			
		||||
            "2F2F677265677765697362726F642E636F6DE1EC5A000800006203F49C"
 | 
			
		||||
            "21D5D6E022CB16DE3538F248662FC73C9808B6B90000001D7518687474"
 | 
			
		||||
            "70733A2F2F677265677765697362726F642E636F6DE1EC5A0008000062"
 | 
			
		||||
            "03F49C21D5D6E022CB16DE3538F248662FC73C9C28BBAC000000127518"
 | 
			
		||||
            "68747470733A2F2F677265677765697362726F642E636F6DE1EC5A0008"
 | 
			
		||||
            "00006203F49C21D5D6E022CB16DE3538F248662FC73CA048C0A3000000"
 | 
			
		||||
            "07751868747470733A2F2F677265677765697362726F642E636F6DE1EC"
 | 
			
		||||
            "5A000800006203F49C21D5D6E022CB16DE3538F248662FC73CAACE82C5"
 | 
			
		||||
            "00000029751868747470733A2F2F677265677765697362726F642E636F"
 | 
			
		||||
            "6DE1EC5A000800006203F49C21D5D6E022CB16DE3538F248662FC73CAE"
 | 
			
		||||
            "EE87B80000001E751868747470733A2F2F677265677765697362726F64"
 | 
			
		||||
            "2E636F6DE1EC5A000800006203F49C21D5D6E022CB16DE3538F248662F"
 | 
			
		||||
            "C73CB30E8CAF00000013751868747470733A2F2F677265677765697362"
 | 
			
		||||
            "726F642E636F6DE1EC5A000800006203F49C21D5D6E022CB16DE3538F2"
 | 
			
		||||
            "48662FC73CB72E91A200000008751868747470733A2F2F677265677765"
 | 
			
		||||
            "697362726F642E636F6DE1EC5A000800006203F49C21D5D6E022CB16DE"
 | 
			
		||||
            "3538F248662FC73CC1B453C40000002A751868747470733A2F2F677265"
 | 
			
		||||
            "677765697362726F642E636F6DE1EC5A000800006203F49C21D5D6E022"
 | 
			
		||||
            "CB16DE3538F248662FC73CC5D458BB0000001F751868747470733A2F2F"
 | 
			
		||||
            "677265677765697362726F642E636F6DE1EC5A000800006203F49C21D5"
 | 
			
		||||
            "D6E022CB16DE3538F248662FC73CC9F45DAE0000001475186874747073"
 | 
			
		||||
            "3A2F2F677265677765697362726F642E636F6DE1EC5A000800006203F4"
 | 
			
		||||
            "9C21D5D6E022CB16DE3538F248662FC73CCE1462A50000000975186874"
 | 
			
		||||
            "7470733A2F2F677265677765697362726F642E636F6DE1EC5A00080000"
 | 
			
		||||
            "6203F49C21D5D6E022CB16DE3538F248662FC73CD89A24C70000002B75"
 | 
			
		||||
            "1868747470733A2F2F677265677765697362726F642E636F6DE1EC5A00"
 | 
			
		||||
            "0800006203F49C21D5D6E022CB16DE3538F248662FC73CDCBA29BA0000"
 | 
			
		||||
            "0020751868747470733A2F2F677265677765697362726F642E636F6DE1"
 | 
			
		||||
            "EC5A000800006203F49C21D5D6E022CB16DE3538F248662FC73CE0DA2E"
 | 
			
		||||
            "B100000015751868747470733A2F2F677265677765697362726F642E63"
 | 
			
		||||
            "6F6DE1EC5A000800006203F49C21D5D6E022CB16DE3538F248662FC73C"
 | 
			
		||||
            "E4FA33A40000000A751868747470733A2F2F677265677765697362726F"
 | 
			
		||||
            "642E636F6DE1EC5A000800006203F49C21D5D6E022CB16DE3538F24866"
 | 
			
		||||
            "2FC73CEF7FF5C60000002C751868747470733A2F2F6772656777656973"
 | 
			
		||||
            "62726F642E636F6DE1EC5A000800006203F49C21D5D6E022CB16DE3538"
 | 
			
		||||
            "F248662FC73CF39FFABD00000021751868747470733A2F2F6772656777"
 | 
			
		||||
            "65697362726F642E636F6DE1EC5A000800006203F49C21D5D6E022CB16"
 | 
			
		||||
            "DE3538F248662FC73CF7BFFFB000000016751868747470733A2F2F6772"
 | 
			
		||||
            "65677765697362726F642E636F6DE1EC5A000800006203F49C21D5D6E0"
 | 
			
		||||
            "22CB16DE3538F248662FC73CFBE004A70000000B751868747470733A2F"
 | 
			
		||||
            "2F677265677765697362726F642E636F6DE1F1E1E1E511006125001F71"
 | 
			
		||||
            "B3556ED9C9459001E4F4A9121F4E07AB6D14898A5BBEF13D85C25D7435"
 | 
			
		||||
            "40DB59F3CF56BE121B82D5812149D633F605EB07265A80B762A365CE94"
 | 
			
		||||
            "883089FEEE4B955701E6240011CC9B202B0000002C6240000002540BE3"
 | 
			
		||||
            "ECE1E72200000000240011CC9C2D0000000A202B0000002D202C000000"
 | 
			
		||||
            "066240000002540BE3E081146203F49C21D5D6E022CB16DE3538F24866"
 | 
			
		||||
            "2FC73CE1E1F1031000";
 | 
			
		||||
 | 
			
		||||
        auto const metaBlob = hexStringToBinaryString(txnMeta);
 | 
			
		||||
        auto const txnBlob = hexStringToBinaryString(txnHex);
 | 
			
		||||
 | 
			
		||||
        ripple::SerialIter it{txnBlob.data(), txnBlob.size()};
 | 
			
		||||
        return {ripple::STTx{it}, ripple::TxMeta{hash, SEQ, metaBlob}};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static auto
 | 
			
		||||
    CreateTransaction(ripple::TxType type)
 | 
			
		||||
    {
 | 
			
		||||
        auto [sttx, meta] = CreateNftTxAndMeta();
 | 
			
		||||
        return etlng::model::Transaction{
 | 
			
		||||
            .raw = "",
 | 
			
		||||
            .metaRaw = "",
 | 
			
		||||
            .sttx = sttx,
 | 
			
		||||
            .meta = meta,
 | 
			
		||||
            .id = ripple::uint256{"0000000000000000000000000000000000000000000000000000000000000001"},
 | 
			
		||||
            .key = "0000000000000000000000000000000000000000000000000000000000000001",
 | 
			
		||||
            .type = type
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static auto
 | 
			
		||||
    CreateObject()
 | 
			
		||||
    {
 | 
			
		||||
        return etlng::model::Object{
 | 
			
		||||
            .key = {},
 | 
			
		||||
            .keyRaw = {},
 | 
			
		||||
            .data = {},
 | 
			
		||||
            .dataRaw = {},
 | 
			
		||||
            .successor = {},
 | 
			
		||||
            .predecessor = {},
 | 
			
		||||
            .type = {},
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace
 | 
			
		||||
 | 
			
		||||
TEST_F(RegistryTest, FilteringOfTxWorksCorrectlyForInitialTransaction)
 | 
			
		||||
{
 | 
			
		||||
    auto transactions = std::vector{
 | 
			
		||||
        CreateTransaction(ripple::TxType::ttNFTOKEN_BURN),
 | 
			
		||||
        CreateTransaction(ripple::TxType::ttNFTOKEN_BURN),
 | 
			
		||||
        CreateTransaction(ripple::TxType::ttNFTOKEN_CREATE_OFFER),
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    auto extBurn = MockExtNftBurn{};
 | 
			
		||||
    auto extOffer = MockExtNftOffer{};
 | 
			
		||||
 | 
			
		||||
    EXPECT_CALL(extBurn, onInitialTransaction(testing::_, testing::_)).Times(2);  // 2 burn txs
 | 
			
		||||
    EXPECT_CALL(extOffer, onInitialTransaction(testing::_, testing::_));          // 1 create offer
 | 
			
		||||
 | 
			
		||||
    auto const header = CreateLedgerHeader(LEDGERHASH, SEQ);
 | 
			
		||||
    auto reg = Registry<MockExtNftBurn&, MockExtNftOffer&>(extBurn, extOffer);
 | 
			
		||||
    reg.dispatchInitialData(etlng::model::LedgerData{
 | 
			
		||||
        .transactions = transactions,
 | 
			
		||||
        .objects = {},
 | 
			
		||||
        .successors = {},
 | 
			
		||||
        .header = header,
 | 
			
		||||
        .rawHeader = {},
 | 
			
		||||
        .seq = SEQ,
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(RegistryTest, FilteringOfTxWorksCorrectlyForTransaction)
 | 
			
		||||
{
 | 
			
		||||
    auto transactions = std::vector{
 | 
			
		||||
        CreateTransaction(ripple::TxType::ttNFTOKEN_BURN),
 | 
			
		||||
        CreateTransaction(ripple::TxType::ttNFTOKEN_BURN),
 | 
			
		||||
        CreateTransaction(ripple::TxType::ttNFTOKEN_CREATE_OFFER),
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    auto extBurn = MockExtTransactionNftBurn{};
 | 
			
		||||
    auto extOffer = MockExtTransactionNftOffer{};
 | 
			
		||||
 | 
			
		||||
    EXPECT_CALL(extBurn, onTransaction(testing::_, testing::_)).Times(2);  // 2 burn txs
 | 
			
		||||
    EXPECT_CALL(extOffer, onTransaction(testing::_, testing::_));          // 1 create offer
 | 
			
		||||
 | 
			
		||||
    auto const header = CreateLedgerHeader(LEDGERHASH, SEQ);
 | 
			
		||||
    auto reg = Registry<MockExtTransactionNftBurn&, MockExtTransactionNftOffer&>(extBurn, extOffer);
 | 
			
		||||
    reg.dispatch(etlng::model::LedgerData{
 | 
			
		||||
        .transactions = std::move(transactions),
 | 
			
		||||
        .objects = {},
 | 
			
		||||
        .successors = {},
 | 
			
		||||
        .header = header,
 | 
			
		||||
        .rawHeader = {},
 | 
			
		||||
        .seq = SEQ
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(RegistryTest, InitialObjectsEmpty)
 | 
			
		||||
{
 | 
			
		||||
    auto extObj = MockExtInitialObject{};
 | 
			
		||||
    auto extObjs = MockExtInitialObjects{};
 | 
			
		||||
 | 
			
		||||
    EXPECT_CALL(extObj, onInitialObject(testing::_, testing::_)).Times(0);       // 0 empty objects sent
 | 
			
		||||
    EXPECT_CALL(extObjs, onInitialObjects(testing::_, testing::_, testing::_));  // 1 vector passed as is
 | 
			
		||||
 | 
			
		||||
    auto reg = Registry<MockExtInitialObject&, MockExtInitialObjects&>(extObj, extObjs);
 | 
			
		||||
    reg.dispatchInitialObjects(SEQ, {}, {});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(RegistryTest, InitialObjectsDispatched)
 | 
			
		||||
{
 | 
			
		||||
    auto extObj = MockExtInitialObject{};
 | 
			
		||||
    auto extObjs = MockExtInitialObjects{};
 | 
			
		||||
 | 
			
		||||
    EXPECT_CALL(extObj, onInitialObject(testing::_, testing::_)).Times(3);       // 3 objects sent
 | 
			
		||||
    EXPECT_CALL(extObjs, onInitialObjects(testing::_, testing::_, testing::_));  // 1 vector passed as is
 | 
			
		||||
 | 
			
		||||
    auto reg = Registry<MockExtInitialObject&, MockExtInitialObjects&>(extObj, extObjs);
 | 
			
		||||
    reg.dispatchInitialObjects(SEQ, {CreateObject(), CreateObject(), CreateObject()}, {});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(RegistryTest, ObjectsDispatched)
 | 
			
		||||
{
 | 
			
		||||
    auto extObj = MockExtOnObject{};
 | 
			
		||||
 | 
			
		||||
    EXPECT_CALL(extObj, onObject(testing::_, testing::_)).Times(3);  // 3 objects sent
 | 
			
		||||
 | 
			
		||||
    auto const header = CreateLedgerHeader(LEDGERHASH, SEQ);
 | 
			
		||||
    auto reg = Registry<MockExtOnObject&>(extObj);
 | 
			
		||||
    reg.dispatch(etlng::model::LedgerData{
 | 
			
		||||
        .transactions = {},
 | 
			
		||||
        .objects = {CreateObject(), CreateObject(), CreateObject()},
 | 
			
		||||
        .successors = {},
 | 
			
		||||
        .header = header,
 | 
			
		||||
        .rawHeader = {},
 | 
			
		||||
        .seq = SEQ
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(RegistryTest, OnLedgerDataForBatch)
 | 
			
		||||
{
 | 
			
		||||
    auto transactions = std::vector{
 | 
			
		||||
        CreateTransaction(ripple::TxType::ttNFTOKEN_BURN),
 | 
			
		||||
        CreateTransaction(ripple::TxType::ttNFTOKEN_BURN),
 | 
			
		||||
        CreateTransaction(ripple::TxType::ttNFTOKEN_CREATE_OFFER),
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    auto ext = MockExtLedgerData{};
 | 
			
		||||
 | 
			
		||||
    EXPECT_CALL(ext, onLedgerData(testing::_));  // 1 batch (dispatch call)
 | 
			
		||||
 | 
			
		||||
    auto const header = CreateLedgerHeader(LEDGERHASH, SEQ);
 | 
			
		||||
    auto reg = Registry<MockExtLedgerData&>(ext);
 | 
			
		||||
    reg.dispatch(etlng::model::LedgerData{
 | 
			
		||||
        .transactions = std::move(transactions),
 | 
			
		||||
        .objects = {},
 | 
			
		||||
        .successors = {},
 | 
			
		||||
        .header = header,
 | 
			
		||||
        .rawHeader = {},
 | 
			
		||||
        .seq = SEQ
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(RegistryTest, InitialObjectsCorrectOrderOfHookCalls)
 | 
			
		||||
{
 | 
			
		||||
    auto extObjs = MockExtInitialObjects{};
 | 
			
		||||
    auto extObj = MockExtInitialObject{};
 | 
			
		||||
 | 
			
		||||
    testing::InSequence seqGuard;
 | 
			
		||||
    EXPECT_CALL(extObjs, onInitialObjects);
 | 
			
		||||
    EXPECT_CALL(extObj, onInitialObject).Times(3);
 | 
			
		||||
 | 
			
		||||
    auto reg = Registry<MockExtInitialObject&, MockExtInitialObjects&>(extObj, extObjs);
 | 
			
		||||
    reg.dispatchInitialObjects(SEQ, {CreateObject(), CreateObject(), CreateObject()}, {});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(RegistryTest, InitialDataCorrectOrderOfHookCalls)
 | 
			
		||||
{
 | 
			
		||||
    auto extInitialData = MockExtInitialData{};
 | 
			
		||||
    auto extInitialTransaction = MockExtNftBurn{};
 | 
			
		||||
 | 
			
		||||
    auto transactions = std::vector{
 | 
			
		||||
        CreateTransaction(ripple::TxType::ttNFTOKEN_BURN),
 | 
			
		||||
        CreateTransaction(ripple::TxType::ttNFTOKEN_BURN),
 | 
			
		||||
        CreateTransaction(ripple::TxType::ttNFTOKEN_CREATE_OFFER),
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    testing::InSequence seqGuard;
 | 
			
		||||
    EXPECT_CALL(extInitialData, onInitialData);
 | 
			
		||||
    EXPECT_CALL(extInitialTransaction, onInitialTransaction).Times(2);
 | 
			
		||||
 | 
			
		||||
    auto const header = CreateLedgerHeader(LEDGERHASH, SEQ);
 | 
			
		||||
    auto reg = Registry<MockExtNftBurn&, MockExtInitialData&>(extInitialTransaction, extInitialData);
 | 
			
		||||
    reg.dispatchInitialData(etlng::model::LedgerData{
 | 
			
		||||
        .transactions = std::move(transactions),
 | 
			
		||||
        .objects = {},
 | 
			
		||||
        .successors = {},
 | 
			
		||||
        .header = header,
 | 
			
		||||
        .rawHeader = {},
 | 
			
		||||
        .seq = SEQ
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(RegistryTest, LedgerDataCorrectOrderOfHookCalls)
 | 
			
		||||
{
 | 
			
		||||
    auto extLedgerData = MockExtLedgerData{};
 | 
			
		||||
    auto extOnTransaction = MockExtTransactionNftBurn{};
 | 
			
		||||
    auto extOnObject = MockExtOnObject{};
 | 
			
		||||
 | 
			
		||||
    auto transactions = std::vector{
 | 
			
		||||
        CreateTransaction(ripple::TxType::ttNFTOKEN_BURN),
 | 
			
		||||
        CreateTransaction(ripple::TxType::ttNFTOKEN_BURN),
 | 
			
		||||
        CreateTransaction(ripple::TxType::ttNFTOKEN_CREATE_OFFER),
 | 
			
		||||
    };
 | 
			
		||||
    auto objects = std::vector{
 | 
			
		||||
        CreateObject(),
 | 
			
		||||
        CreateObject(),
 | 
			
		||||
        CreateObject(),
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // testing::Sequence seq;
 | 
			
		||||
    testing::InSequence seqGuard;
 | 
			
		||||
    EXPECT_CALL(extLedgerData, onLedgerData);
 | 
			
		||||
    EXPECT_CALL(extOnTransaction, onTransaction).Times(2);
 | 
			
		||||
    EXPECT_CALL(extOnObject, onObject).Times(3);
 | 
			
		||||
 | 
			
		||||
    auto const header = CreateLedgerHeader(LEDGERHASH, SEQ);
 | 
			
		||||
    auto reg = Registry<MockExtOnObject&, MockExtTransactionNftBurn&, MockExtLedgerData&>(
 | 
			
		||||
        extOnObject, extOnTransaction, extLedgerData
 | 
			
		||||
    );
 | 
			
		||||
    reg.dispatch(etlng::model::LedgerData{
 | 
			
		||||
        .transactions = std::move(transactions),
 | 
			
		||||
        .objects = std::move(objects),
 | 
			
		||||
        .successors = {},
 | 
			
		||||
        .header = header,
 | 
			
		||||
        .rawHeader = {},
 | 
			
		||||
        .seq = SEQ
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user