mirror of
				https://github.com/XRPLF/clio.git
				synced 2025-11-04 11:55:51 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1447 lines
		
	
	
		
			68 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1447 lines
		
	
	
		
			68 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
//------------------------------------------------------------------------------
 | 
						|
/*
 | 
						|
    This file is part of clio: https://github.com/XRPLF/clio
 | 
						|
    Copyright (c) 2023, 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 "data/BackendInterface.hpp"
 | 
						|
#include "data/CassandraBackend.hpp"
 | 
						|
#include "data/DBHelpers.hpp"
 | 
						|
#include "data/LedgerCache.hpp"
 | 
						|
#include "data/LedgerHeaderCache.hpp"
 | 
						|
#include "data/Types.hpp"
 | 
						|
#include "data/cassandra/Handle.hpp"
 | 
						|
#include "data/cassandra/SettingsProvider.hpp"
 | 
						|
#include "data/cassandra/Types.hpp"
 | 
						|
#include "etl/NFTHelpers.hpp"
 | 
						|
#include "rpc/RPCHelpers.hpp"
 | 
						|
#include "util/AsioContextTestFixture.hpp"
 | 
						|
#include "util/LedgerUtils.hpp"
 | 
						|
#include "util/MockLedgerHeaderCache.hpp"
 | 
						|
#include "util/MockPrometheus.hpp"
 | 
						|
#include "util/Random.hpp"
 | 
						|
#include "util/StringUtils.hpp"
 | 
						|
#include "util/config/ConfigValue.hpp"
 | 
						|
#include "util/config/ObjectView.hpp"
 | 
						|
#include "util/config/Types.hpp"
 | 
						|
 | 
						|
#include <TestGlobals.hpp>
 | 
						|
#include <boost/asio/impl/spawn.hpp>
 | 
						|
#include <boost/asio/io_context.hpp>
 | 
						|
#include <boost/asio/spawn.hpp>
 | 
						|
#include <boost/uuid/random_generator.hpp>
 | 
						|
#include <boost/uuid/uuid.hpp>
 | 
						|
#include <boost/uuid/uuid_hash.hpp>
 | 
						|
#include <gmock/gmock.h>
 | 
						|
#include <gtest/gtest.h>
 | 
						|
#include <xrpl/basics/Slice.h>
 | 
						|
#include <xrpl/basics/base_uint.h>
 | 
						|
#include <xrpl/basics/strHex.h>
 | 
						|
#include <xrpl/protocol/AccountID.h>
 | 
						|
#include <xrpl/protocol/LedgerHeader.h>
 | 
						|
#include <xrpl/protocol/STTx.h>
 | 
						|
#include <xrpl/protocol/Serializer.h>
 | 
						|
#include <xrpl/protocol/TxMeta.h>
 | 
						|
 | 
						|
#include <algorithm>
 | 
						|
#include <atomic>
 | 
						|
#include <chrono>
 | 
						|
#include <cstddef>
 | 
						|
#include <cstdint>
 | 
						|
#include <cstring>
 | 
						|
#include <map>
 | 
						|
#include <memory>
 | 
						|
#include <optional>
 | 
						|
#include <random>
 | 
						|
#include <string>
 | 
						|
#include <string_view>
 | 
						|
#include <thread>
 | 
						|
#include <tuple>
 | 
						|
#include <unordered_map>
 | 
						|
#include <utility>
 | 
						|
#include <vector>
 | 
						|
 | 
						|
using namespace util;
 | 
						|
using namespace util::config;
 | 
						|
using namespace std;
 | 
						|
using namespace rpc;
 | 
						|
using namespace prometheus;
 | 
						|
 | 
						|
using namespace data::cassandra;
 | 
						|
 | 
						|
class BackendCassandraTestBase : public SyncAsioContextTest, public WithPrometheus {
 | 
						|
protected:
 | 
						|
    ClioConfigDefinition cfg_{
 | 
						|
        {"database.type", ConfigValue{ConfigType::String}.defaultValue("cassandra")},
 | 
						|
        {"database.cassandra.contact_points",
 | 
						|
         ConfigValue{ConfigType::String}.defaultValue(TestGlobals::instance().backendHost)},
 | 
						|
        {"database.cassandra.secure_connect_bundle", ConfigValue{ConfigType::String}.optional()},
 | 
						|
        {"database.cassandra.port", ConfigValue{ConfigType::Integer}.optional()},
 | 
						|
        {"database.cassandra.keyspace",
 | 
						|
         ConfigValue{ConfigType::String}.defaultValue(TestGlobals::instance().backendKeyspace)},
 | 
						|
        {"database.cassandra.replication_factor", ConfigValue{ConfigType::Integer}.defaultValue(1)},
 | 
						|
        {"database.cassandra.table_prefix", ConfigValue{ConfigType::String}.optional()},
 | 
						|
        {"database.cassandra.max_write_requests_outstanding", ConfigValue{ConfigType::Integer}.defaultValue(10'000)},
 | 
						|
        {"database.cassandra.max_read_requests_outstanding", ConfigValue{ConfigType::Integer}.defaultValue(100'000)},
 | 
						|
        {"database.cassandra.threads",
 | 
						|
         ConfigValue{ConfigType::Integer}.defaultValue(static_cast<uint32_t>(std::thread::hardware_concurrency()))},
 | 
						|
        {"database.cassandra.core_connections_per_host", ConfigValue{ConfigType::Integer}.defaultValue(1)},
 | 
						|
        {"database.cassandra.queue_size_io", ConfigValue{ConfigType::Integer}.optional()},
 | 
						|
        {"database.cassandra.write_batch_size", ConfigValue{ConfigType::Integer}.defaultValue(20)},
 | 
						|
        {"database.cassandra.connect_timeout", ConfigValue{ConfigType::Integer}.defaultValue(10).optional()},
 | 
						|
        {"database.cassandra.request_timeout", ConfigValue{ConfigType::Integer}.defaultValue(10).optional()},
 | 
						|
        {"database.cassandra.username", ConfigValue{ConfigType::String}.optional()},
 | 
						|
        {"database.cassandra.password", ConfigValue{ConfigType::String}.optional()},
 | 
						|
        {"database.cassandra.certfile", ConfigValue{ConfigType::String}.optional()},
 | 
						|
 | 
						|
        {"read_only", ConfigValue{ConfigType::Boolean}.defaultValue(false)}
 | 
						|
    };
 | 
						|
 | 
						|
    static constexpr auto kRAWHEADER =
 | 
						|
        "03C3141A01633CD656F91B4EBB5EB89B791BD34DBC8A04BB6F407C5335BC54351E"
 | 
						|
        "DD733898497E809E04074D14D271E4832D7888754F9230800761563A292FA2315A"
 | 
						|
        "6DB6FE30CC5909B285080FCD6773CC883F9FE0EE4D439340AC592AADB973ED3CF5"
 | 
						|
        "3E2232B33EF57CECAC2816E3122816E31A0A00F8377CD95DFA484CFAE282656A58"
 | 
						|
        "CE5AA29652EFFD80AC59CD91416E4E13DBBE";
 | 
						|
 | 
						|
    ObjectView obj_ = cfg_.getObject("database.cassandra");
 | 
						|
    SettingsProvider settingsProvider_{obj_};
 | 
						|
 | 
						|
    // recreated for each test
 | 
						|
    data::LedgerCache cache_;
 | 
						|
 | 
						|
    std::default_random_engine randomEngine_{0};
 | 
						|
 | 
						|
public:
 | 
						|
    ~BackendCassandraTestBase() override
 | 
						|
    {
 | 
						|
        // drop the keyspace for next test
 | 
						|
        Handle const handle{TestGlobals::instance().backendHost};
 | 
						|
        EXPECT_TRUE(handle.connect());
 | 
						|
        handle.execute("DROP KEYSPACE " + TestGlobals::instance().backendKeyspace);
 | 
						|
    }
 | 
						|
};
 | 
						|
 | 
						|
class BackendCassandraTest : public BackendCassandraTestBase {
 | 
						|
protected:
 | 
						|
    std::unique_ptr<BackendInterface> backend_{std::make_unique<CassandraBackend>(settingsProvider_, cache_, false)};
 | 
						|
};
 | 
						|
 | 
						|
TEST_F(BackendCassandraTest, Basic)
 | 
						|
{
 | 
						|
    std::atomic_bool done = false;
 | 
						|
    std::optional<boost::asio::io_context::work> work;
 | 
						|
    work.emplace(ctx_);
 | 
						|
 | 
						|
    boost::asio::spawn(ctx_, [this, &done, &work](boost::asio::yield_context yield) {
 | 
						|
        std::string const rawHeader =
 | 
						|
            "03C3141A01633CD656F91B4EBB5EB89B791BD34DBC8A04BB6F407C5335BC54351E"
 | 
						|
            "DD733898497E809E04074D14D271E4832D7888754F9230800761563A292FA2315A"
 | 
						|
            "6DB6FE30CC5909B285080FCD6773CC883F9FE0EE4D439340AC592AADB973ED3CF5"
 | 
						|
            "3E2232B33EF57CECAC2816E3122816E31A0A00F8377CD95DFA484CFAE282656A58"
 | 
						|
            "CE5AA29652EFFD80AC59CD91416E4E13DBBE";
 | 
						|
 | 
						|
        std::string rawHeaderBlob = hexStringToBinaryString(rawHeader);
 | 
						|
        ripple::LedgerHeader const lgrInfo = util::deserializeHeader(ripple::makeSlice(rawHeaderBlob));
 | 
						|
 | 
						|
        backend_->writeLedger(lgrInfo, std::move(rawHeaderBlob));
 | 
						|
        backend_->writeSuccessor(uint256ToString(data::kFIRST_KEY), lgrInfo.seq, uint256ToString(data::kLAST_KEY));
 | 
						|
        ASSERT_TRUE(backend_->finishWrites(lgrInfo.seq));
 | 
						|
        {
 | 
						|
            auto rng = backend_->fetchLedgerRange();
 | 
						|
            ASSERT_TRUE(rng.has_value());
 | 
						|
            EXPECT_EQ(rng->minSequence, rng->maxSequence);
 | 
						|
            EXPECT_EQ(rng->maxSequence, lgrInfo.seq);
 | 
						|
        }
 | 
						|
        {
 | 
						|
            auto seq = backend_->fetchLatestLedgerSequence(yield);
 | 
						|
            ASSERT_TRUE(seq.has_value());
 | 
						|
            EXPECT_EQ(*seq, lgrInfo.seq);
 | 
						|
        }
 | 
						|
        {
 | 
						|
            auto retLgr = backend_->fetchLedgerBySequence(lgrInfo.seq, yield);
 | 
						|
            ASSERT_TRUE(retLgr.has_value());
 | 
						|
            EXPECT_EQ(retLgr->seq, lgrInfo.seq);
 | 
						|
            EXPECT_EQ(ledgerHeaderToBlob(lgrInfo), ledgerHeaderToBlob(*retLgr));
 | 
						|
        }
 | 
						|
 | 
						|
        EXPECT_FALSE(backend_->fetchLedgerBySequence(lgrInfo.seq + 1, yield).has_value());
 | 
						|
        auto lgrInfoOld = lgrInfo;
 | 
						|
 | 
						|
        auto lgrInfoNext = lgrInfo;
 | 
						|
        lgrInfoNext.seq = lgrInfo.seq + 1;
 | 
						|
        lgrInfoNext.parentHash = lgrInfo.hash;
 | 
						|
        lgrInfoNext.hash++;
 | 
						|
        lgrInfoNext.accountHash = ~lgrInfo.accountHash;
 | 
						|
        {
 | 
						|
            std::string infoBlob = ledgerHeaderToBinaryString(lgrInfoNext);
 | 
						|
 | 
						|
            backend_->writeLedger(lgrInfoNext, std::move(infoBlob));
 | 
						|
            ASSERT_TRUE(backend_->finishWrites(lgrInfoNext.seq));
 | 
						|
        }
 | 
						|
        {
 | 
						|
            auto rng = backend_->fetchLedgerRange();
 | 
						|
            EXPECT_TRUE(rng.has_value());
 | 
						|
            EXPECT_EQ(rng->minSequence, lgrInfoOld.seq);
 | 
						|
            EXPECT_EQ(rng->maxSequence, lgrInfoNext.seq);
 | 
						|
        }
 | 
						|
        {
 | 
						|
            auto seq = backend_->fetchLatestLedgerSequence(yield);
 | 
						|
            EXPECT_EQ(seq, lgrInfoNext.seq);
 | 
						|
        }
 | 
						|
        {
 | 
						|
            auto retLgr = backend_->fetchLedgerBySequence(lgrInfoNext.seq, yield);
 | 
						|
            EXPECT_TRUE(retLgr.has_value());
 | 
						|
            EXPECT_EQ(retLgr->seq, lgrInfoNext.seq);
 | 
						|
            EXPECT_EQ(ledgerHeaderToBlob(*retLgr), ledgerHeaderToBlob(lgrInfoNext));
 | 
						|
            EXPECT_NE(ledgerHeaderToBlob(*retLgr), ledgerHeaderToBlob(lgrInfoOld));
 | 
						|
            retLgr = backend_->fetchLedgerBySequence(lgrInfoNext.seq - 1, yield);
 | 
						|
            EXPECT_EQ(ledgerHeaderToBlob(*retLgr), ledgerHeaderToBlob(lgrInfoOld));
 | 
						|
            EXPECT_NE(ledgerHeaderToBlob(*retLgr), ledgerHeaderToBlob(lgrInfoNext));
 | 
						|
            retLgr = backend_->fetchLedgerBySequence(lgrInfoNext.seq - 2, yield);
 | 
						|
            EXPECT_FALSE(backend_->fetchLedgerBySequence(lgrInfoNext.seq - 2, yield).has_value());
 | 
						|
 | 
						|
            auto txns = backend_->fetchAllTransactionsInLedger(lgrInfoNext.seq, yield);
 | 
						|
            EXPECT_EQ(txns.size(), 0);
 | 
						|
 | 
						|
            auto hashes = backend_->fetchAllTransactionHashesInLedger(lgrInfoNext.seq, yield);
 | 
						|
            EXPECT_EQ(hashes.size(), 0);
 | 
						|
        }
 | 
						|
 | 
						|
        // the below dummy data is not expected to be consistent. The
 | 
						|
        // metadata string does represent valid metadata. Don't assume
 | 
						|
        // though that the transaction or its hash correspond to the
 | 
						|
        // metadata, or anything like that. These tests are purely
 | 
						|
        // binary tests to make sure the same data that goes in, comes
 | 
						|
        // back out
 | 
						|
        std::string const metaHex =
 | 
						|
            "201C0000001AF8E411006F560A3E08122A05AC91DEFA87052B0554E4A29B46"
 | 
						|
            "3A27642EBB060B6052196592EEE72200000000240480FDB52503CE1A863300"
 | 
						|
            "000000000000003400000000000000005529983CBAED30F547471452921C3C"
 | 
						|
            "6B9F9685F292F6291000EED0A44413AF18C250101AC09600F4B502C8F7F830"
 | 
						|
            "F80B616DCB6F3970CB79AB70975A05ED5B66860B9564400000001FE217CB65"
 | 
						|
            "D54B640B31521B05000000000000000000000000434E5900000000000360E3"
 | 
						|
            "E0751BD9A566CD03FA6CAFC78118B82BA081142252F328CF91263417762570"
 | 
						|
            "D67220CCB33B1370E1E1E3110064561AC09600F4B502C8F7F830F80B616DCB"
 | 
						|
            "6F3970CB79AB70975A05ED33DF783681E8365A05ED33DF783681581AC09600"
 | 
						|
            "F4B502C8F7F830F80B616DCB6F3970CB79AB70975A05ED33DF783681031100"
 | 
						|
            "0000000000000000000000434E59000000000004110360E3E0751BD9A566CD"
 | 
						|
            "03FA6CAFC78118B82BA0E1E1E4110064561AC09600F4B502C8F7F830F80B61"
 | 
						|
            "6DCB6F3970CB79AB70975A05ED5B66860B95E72200000000365A05ED5B6686"
 | 
						|
            "0B95581AC09600F4B502C8F7F830F80B616DCB6F3970CB79AB70975A05ED5B"
 | 
						|
            "66860B95011100000000000000000000000000000000000000000211000000"
 | 
						|
            "00000000000000000000000000000000000311000000000000000000000000"
 | 
						|
            "434E59000000000004110360E3E0751BD9A566CD03FA6CAFC78118B82BA0E1"
 | 
						|
            "E1E311006F5647B05E66DE9F3DF2689E8F4CE6126D3136B6C5E79587F9D24B"
 | 
						|
            "D71A952B0852BAE8240480FDB950101AC09600F4B502C8F7F830F80B616DCB"
 | 
						|
            "6F3970CB79AB70975A05ED33DF78368164400000033C83A95F65D59D9A6291"
 | 
						|
            "9C2D18000000000000000000000000434E5900000000000360E3E0751BD9A5"
 | 
						|
            "66CD03FA6CAFC78118B82BA081142252F328CF91263417762570D67220CCB3"
 | 
						|
            "3B1370E1E1E511006456AEA3074F10FE15DAC592F8A0405C61FB7D4C98F588"
 | 
						|
            "C2D55C84718FAFBBD2604AE722000000003100000000000000003200000000"
 | 
						|
            "0000000058AEA3074F10FE15DAC592F8A0405C61FB7D4C98F588C2D55C8471"
 | 
						|
            "8FAFBBD2604A82142252F328CF91263417762570D67220CCB33B1370E1E1E5"
 | 
						|
            "1100612503CE1A8755CE935137F8C6C8DEF26B5CD93BE18105CA83F65E1E90"
 | 
						|
            "CEC546F562D25957DC0856E0311EB450B6177F969B94DBDDA83E99B7A0576A"
 | 
						|
            "CD9079573876F16C0C004F06E6240480FDB9624000000005FF0E2BE1E72200"
 | 
						|
            "000000240480FDBA2D00000005624000000005FF0E1F81142252F328CF9126"
 | 
						|
            "3417762570D67220CCB33B1370E1E1F1031000";
 | 
						|
        std::string const txnHex =
 | 
						|
            "1200072200000000240480FDB920190480FDB5201B03CE1A8964400000033C"
 | 
						|
            "83A95F65D59D9A62919C2D18000000000000000000000000434E5900000000"
 | 
						|
            "000360E3E0751BD9A566CD03FA6CAFC78118B82BA068400000000000000C73"
 | 
						|
            "21022D40673B44C82DEE1DDB8B9BB53DCCE4F97B27404DB850F068DD91D685"
 | 
						|
            "E337EA7446304402202EA6B702B48B39F2197112382838F92D4C02948E9911"
 | 
						|
            "FE6B2DEBCF9183A426BC022005DAC06CD4517E86C2548A80996019F3AC60A0"
 | 
						|
            "9EED153BF60C992930D68F09F981142252F328CF91263417762570D67220CC"
 | 
						|
            "B33B1370";
 | 
						|
        std::string const hashHex = "0A81FB3D6324C2DCF73131505C6E4DC67981D7FC39F5E9574CEC4B1F22D28BF7";
 | 
						|
 | 
						|
        // this account is not related to the above transaction and
 | 
						|
        // metadata
 | 
						|
        std::string const accountHex =
 | 
						|
            "1100612200000000240480FDBC2503CE1A872D0000000555516931B2AD018EFFBE"
 | 
						|
            "17C5C9DCCF872F36837C2C6136ACF80F2A24079CF81FD0624000000005FF0E0781"
 | 
						|
            "142252F328CF91263417762570D67220CCB33B1370";
 | 
						|
        std::string const accountIndexHex = "E0311EB450B6177F969B94DBDDA83E99B7A0576ACD9079573876F16C0C004F06";
 | 
						|
 | 
						|
        // An NFTokenMint tx
 | 
						|
        std::string const nftTxnHex =
 | 
						|
            "1200192200000008240011CC9B201B001F71D6202A0000000168400000"
 | 
						|
            "000000000C7321ED475D1452031E8F9641AF1631519A58F7B8681E172E"
 | 
						|
            "4838AA0E59408ADA1727DD74406960041F34F10E0CBB39444B4D4E577F"
 | 
						|
            "C0B7E8D843D091C2917E96E7EE0E08B30C91413EC551A2B8A1D405E8BA"
 | 
						|
            "34FE185D8B10C53B40928611F2DE3B746F0303751868747470733A2F2F"
 | 
						|
            "677265677765697362726F642E636F6D81146203F49C21D5D6E022CB16"
 | 
						|
            "DE3538F248662FC73C";
 | 
						|
 | 
						|
        std::string const nftTxnMeta =
 | 
						|
            "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";
 | 
						|
        std::string const nftTxnHashHex =
 | 
						|
            "6C7F69A6D25A13AC4A2E9145999F45D4674F939900017A96885FDC2757"
 | 
						|
            "E9284E";
 | 
						|
        ripple::uint256 nftID;
 | 
						|
        EXPECT_TRUE(
 | 
						|
            nftID.parseHex("000800006203F49C21D5D6E022CB16DE3538F248662"
 | 
						|
                           "FC73CEF7FF5C60000002C")
 | 
						|
        );
 | 
						|
 | 
						|
        std::string metaBlob = hexStringToBinaryString(metaHex);
 | 
						|
        std::string txnBlob = hexStringToBinaryString(txnHex);
 | 
						|
        std::string const hashBlob = hexStringToBinaryString(hashHex);
 | 
						|
        std::string accountBlob = hexStringToBinaryString(accountHex);
 | 
						|
        std::string const accountIndexBlob = hexStringToBinaryString(accountIndexHex);
 | 
						|
        std::vector<ripple::AccountID> affectedAccounts;
 | 
						|
 | 
						|
        std::string nftTxnBlob = hexStringToBinaryString(nftTxnHex);
 | 
						|
        std::string const nftTxnMetaBlob = hexStringToBinaryString(nftTxnMeta);
 | 
						|
 | 
						|
        {
 | 
						|
            lgrInfoNext.seq = lgrInfoNext.seq + 1;
 | 
						|
            lgrInfoNext.txHash = ~lgrInfo.txHash;
 | 
						|
            lgrInfoNext.accountHash = lgrInfoNext.accountHash ^ lgrInfoNext.txHash;
 | 
						|
            lgrInfoNext.parentHash = lgrInfoNext.hash;
 | 
						|
            lgrInfoNext.hash++;
 | 
						|
 | 
						|
            ripple::uint256 hash256;
 | 
						|
            EXPECT_TRUE(hash256.parseHex(hashHex));
 | 
						|
            ripple::TxMeta const txMeta{hash256, lgrInfoNext.seq, metaBlob};
 | 
						|
            auto accountsSet = txMeta.getAffectedAccounts();
 | 
						|
            for (auto& a : accountsSet) {
 | 
						|
                affectedAccounts.push_back(a);
 | 
						|
            }
 | 
						|
            std::vector<AccountTransactionsData> accountTxData;
 | 
						|
            accountTxData.emplace_back(txMeta, hash256);
 | 
						|
 | 
						|
            ripple::uint256 nftHash256;
 | 
						|
            EXPECT_TRUE(nftHash256.parseHex(nftTxnHashHex));
 | 
						|
            ripple::TxMeta const nftTxMeta{nftHash256, lgrInfoNext.seq, nftTxnMetaBlob};
 | 
						|
            ripple::SerialIter it{nftTxnBlob.data(), nftTxnBlob.size()};
 | 
						|
            ripple::STTx const sttx{it};
 | 
						|
            auto const [parsedNFTTxsRef, parsedNFT] = etl::getNFTDataFromTx(nftTxMeta, sttx);
 | 
						|
            // need to copy the nft txns so we can std::move later
 | 
						|
            std::vector<NFTTransactionsData> parsedNFTTxs;
 | 
						|
            parsedNFTTxs.insert(parsedNFTTxs.end(), parsedNFTTxsRef.begin(), parsedNFTTxsRef.end());
 | 
						|
            EXPECT_EQ(parsedNFTTxs.size(), 1);
 | 
						|
            EXPECT_TRUE(parsedNFT.has_value());
 | 
						|
            EXPECT_EQ(parsedNFT->tokenID, nftID);
 | 
						|
            std::vector<NFTsData> nftData;
 | 
						|
            nftData.push_back(*parsedNFT);
 | 
						|
 | 
						|
            backend_->writeLedger(lgrInfoNext, ledgerHeaderToBinaryString(lgrInfoNext));
 | 
						|
            backend_->writeTransaction(
 | 
						|
                std::string{hashBlob},
 | 
						|
                lgrInfoNext.seq,
 | 
						|
                lgrInfoNext.closeTime.time_since_epoch().count(),
 | 
						|
                std::string{txnBlob},
 | 
						|
                std::string{metaBlob}
 | 
						|
            );
 | 
						|
            backend_->writeAccountTransactions(std::move(accountTxData));
 | 
						|
            backend_->writeNFTs(nftData);
 | 
						|
            backend_->writeNFTTransactions(parsedNFTTxs);
 | 
						|
 | 
						|
            backend_->writeLedgerObject(std::string{accountIndexBlob}, lgrInfoNext.seq, std::string{accountBlob});
 | 
						|
            backend_->writeSuccessor(uint256ToString(data::kFIRST_KEY), lgrInfoNext.seq, std::string{accountIndexBlob});
 | 
						|
            backend_->writeSuccessor(std::string{accountIndexBlob}, lgrInfoNext.seq, uint256ToString(data::kLAST_KEY));
 | 
						|
 | 
						|
            ASSERT_TRUE(backend_->finishWrites(lgrInfoNext.seq));
 | 
						|
        }
 | 
						|
 | 
						|
        {
 | 
						|
            auto rng = backend_->fetchLedgerRange();
 | 
						|
            EXPECT_TRUE(rng);
 | 
						|
            EXPECT_EQ(rng->minSequence, lgrInfoOld.seq);
 | 
						|
            EXPECT_EQ(rng->maxSequence, lgrInfoNext.seq);
 | 
						|
            auto retLgr = backend_->fetchLedgerBySequence(lgrInfoNext.seq, yield);
 | 
						|
            EXPECT_TRUE(retLgr);
 | 
						|
            EXPECT_EQ(ledgerHeaderToBlob(*retLgr), ledgerHeaderToBlob(lgrInfoNext));
 | 
						|
            auto allTransactions = backend_->fetchAllTransactionsInLedger(lgrInfoNext.seq, yield);
 | 
						|
            ASSERT_EQ(allTransactions.size(), 1);
 | 
						|
            EXPECT_STREQ(
 | 
						|
                reinterpret_cast<char const*>(allTransactions[0].transaction.data()),
 | 
						|
                static_cast<char const*>(txnBlob.data())
 | 
						|
            );
 | 
						|
            EXPECT_STREQ(
 | 
						|
                reinterpret_cast<char const*>(allTransactions[0].metadata.data()),
 | 
						|
                static_cast<char const*>(metaBlob.data())
 | 
						|
            );
 | 
						|
            auto hashes = backend_->fetchAllTransactionHashesInLedger(lgrInfoNext.seq, yield);
 | 
						|
            EXPECT_EQ(hashes.size(), 1);
 | 
						|
            EXPECT_EQ(ripple::strHex(hashes[0]), hashHex);
 | 
						|
            for (auto& a : affectedAccounts) {
 | 
						|
                auto [accountTransactions, cursor] = backend_->fetchAccountTransactions(a, 100, true, {}, yield);
 | 
						|
                EXPECT_EQ(accountTransactions.size(), 1);
 | 
						|
                EXPECT_EQ(accountTransactions[0], accountTransactions[0]);
 | 
						|
                EXPECT_FALSE(cursor);
 | 
						|
            }
 | 
						|
            auto nft = backend_->fetchNFT(nftID, lgrInfoNext.seq, yield);
 | 
						|
            EXPECT_TRUE(nft.has_value());
 | 
						|
            auto [nftTxns, cursor] = backend_->fetchNFTTransactions(nftID, 100, true, {}, yield);
 | 
						|
            EXPECT_EQ(nftTxns.size(), 1);
 | 
						|
            EXPECT_EQ(nftTxns[0], nftTxns[0]);
 | 
						|
            EXPECT_FALSE(cursor);
 | 
						|
 | 
						|
            ripple::uint256 key256;
 | 
						|
            EXPECT_TRUE(key256.parseHex(accountIndexHex));
 | 
						|
            auto obj = backend_->fetchLedgerObject(key256, lgrInfoNext.seq, yield);
 | 
						|
            EXPECT_TRUE(obj);
 | 
						|
            EXPECT_STREQ(reinterpret_cast<char const*>(obj->data()), static_cast<char const*>(accountBlob.data()));
 | 
						|
            obj = backend_->fetchLedgerObject(key256, lgrInfoNext.seq + 1, yield);
 | 
						|
            EXPECT_TRUE(obj);
 | 
						|
            EXPECT_STREQ(reinterpret_cast<char const*>(obj->data()), static_cast<char const*>(accountBlob.data()));
 | 
						|
            obj = backend_->fetchLedgerObject(key256, lgrInfoOld.seq - 1, yield);
 | 
						|
            EXPECT_FALSE(obj);
 | 
						|
        }
 | 
						|
 | 
						|
        std::string accountBlobOld = accountBlob;
 | 
						|
        {
 | 
						|
            lgrInfoNext.seq = lgrInfoNext.seq + 1;
 | 
						|
            lgrInfoNext.parentHash = lgrInfoNext.hash;
 | 
						|
            lgrInfoNext.hash++;
 | 
						|
            lgrInfoNext.txHash = lgrInfoNext.txHash ^ lgrInfoNext.accountHash;
 | 
						|
            lgrInfoNext.accountHash = ~(lgrInfoNext.accountHash ^ lgrInfoNext.txHash);
 | 
						|
 | 
						|
            backend_->writeLedger(lgrInfoNext, ledgerHeaderToBinaryString(lgrInfoNext));
 | 
						|
            std::shuffle(accountBlob.begin(), accountBlob.end(), randomEngine_);
 | 
						|
            backend_->writeLedgerObject(std::string{accountIndexBlob}, lgrInfoNext.seq, std::string{accountBlob});
 | 
						|
 | 
						|
            ASSERT_TRUE(backend_->finishWrites(lgrInfoNext.seq));
 | 
						|
        }
 | 
						|
        {
 | 
						|
            auto rng = backend_->fetchLedgerRange();
 | 
						|
            EXPECT_TRUE(rng);
 | 
						|
            EXPECT_EQ(rng->minSequence, lgrInfoOld.seq);
 | 
						|
            EXPECT_EQ(rng->maxSequence, lgrInfoNext.seq);
 | 
						|
            auto retLgr = backend_->fetchLedgerBySequence(lgrInfoNext.seq, yield);
 | 
						|
            EXPECT_TRUE(retLgr);
 | 
						|
            EXPECT_EQ(ledgerHeaderToBlob(*retLgr), ledgerHeaderToBlob(lgrInfoNext));
 | 
						|
            auto txns = backend_->fetchAllTransactionsInLedger(lgrInfoNext.seq, yield);
 | 
						|
            EXPECT_EQ(txns.size(), 0);
 | 
						|
 | 
						|
            ripple::uint256 key256;
 | 
						|
            EXPECT_TRUE(key256.parseHex(accountIndexHex));
 | 
						|
            auto obj = backend_->fetchLedgerObject(key256, lgrInfoNext.seq, yield);
 | 
						|
            EXPECT_TRUE(obj);
 | 
						|
            EXPECT_STREQ(reinterpret_cast<char const*>(obj->data()), static_cast<char const*>(accountBlob.data()));
 | 
						|
            obj = backend_->fetchLedgerObject(key256, lgrInfoNext.seq + 1, yield);
 | 
						|
            EXPECT_TRUE(obj);
 | 
						|
            EXPECT_STREQ(reinterpret_cast<char const*>(obj->data()), static_cast<char const*>(accountBlob.data()));
 | 
						|
            obj = backend_->fetchLedgerObject(key256, lgrInfoNext.seq - 1, yield);
 | 
						|
            EXPECT_TRUE(obj);
 | 
						|
            EXPECT_STREQ(reinterpret_cast<char const*>(obj->data()), static_cast<char const*>(accountBlobOld.data()));
 | 
						|
            obj = backend_->fetchLedgerObject(key256, lgrInfoOld.seq - 1, yield);
 | 
						|
            EXPECT_FALSE(obj);
 | 
						|
        }
 | 
						|
        {
 | 
						|
            lgrInfoNext.seq = lgrInfoNext.seq + 1;
 | 
						|
            lgrInfoNext.parentHash = lgrInfoNext.hash;
 | 
						|
            lgrInfoNext.hash++;
 | 
						|
            lgrInfoNext.txHash = lgrInfoNext.txHash ^ lgrInfoNext.accountHash;
 | 
						|
            lgrInfoNext.accountHash = ~(lgrInfoNext.accountHash ^ lgrInfoNext.txHash);
 | 
						|
 | 
						|
            backend_->writeLedger(lgrInfoNext, ledgerHeaderToBinaryString(lgrInfoNext));
 | 
						|
            backend_->writeLedgerObject(std::string{accountIndexBlob}, lgrInfoNext.seq, std::string{});
 | 
						|
            backend_->writeSuccessor(
 | 
						|
                uint256ToString(data::kFIRST_KEY), lgrInfoNext.seq, uint256ToString(data::kLAST_KEY)
 | 
						|
            );
 | 
						|
 | 
						|
            ASSERT_TRUE(backend_->finishWrites(lgrInfoNext.seq));
 | 
						|
        }
 | 
						|
        {
 | 
						|
            auto rng = backend_->fetchLedgerRange();
 | 
						|
            EXPECT_TRUE(rng);
 | 
						|
            EXPECT_EQ(rng->minSequence, lgrInfoOld.seq);
 | 
						|
            EXPECT_EQ(rng->maxSequence, lgrInfoNext.seq);
 | 
						|
            auto retLgr = backend_->fetchLedgerBySequence(lgrInfoNext.seq, yield);
 | 
						|
            EXPECT_TRUE(retLgr);
 | 
						|
            EXPECT_EQ(ledgerHeaderToBlob(*retLgr), ledgerHeaderToBlob(lgrInfoNext));
 | 
						|
            auto txns = backend_->fetchAllTransactionsInLedger(lgrInfoNext.seq, yield);
 | 
						|
            EXPECT_EQ(txns.size(), 0);
 | 
						|
 | 
						|
            ripple::uint256 key256;
 | 
						|
            EXPECT_TRUE(key256.parseHex(accountIndexHex));
 | 
						|
            auto obj = backend_->fetchLedgerObject(key256, lgrInfoNext.seq, yield);
 | 
						|
            EXPECT_FALSE(obj);
 | 
						|
            obj = backend_->fetchLedgerObject(key256, lgrInfoNext.seq + 1, yield);
 | 
						|
            EXPECT_FALSE(obj);
 | 
						|
            obj = backend_->fetchLedgerObject(key256, lgrInfoNext.seq - 2, yield);
 | 
						|
            EXPECT_TRUE(obj);
 | 
						|
            EXPECT_STREQ(reinterpret_cast<char const*>(obj->data()), static_cast<char const*>(accountBlobOld.data()));
 | 
						|
            obj = backend_->fetchLedgerObject(key256, lgrInfoOld.seq - 1, yield);
 | 
						|
            EXPECT_FALSE(obj);
 | 
						|
        }
 | 
						|
 | 
						|
        auto generateObjects = [](size_t numObjects, uint32_t ledgerSequence) {
 | 
						|
            std::vector<std::pair<std::string, std::string>> res{numObjects};
 | 
						|
            ripple::uint256 key;
 | 
						|
            key = ledgerSequence * 100000ul;
 | 
						|
 | 
						|
            for (auto& blob : res) {
 | 
						|
                ++key;
 | 
						|
                std::string const keyStr{reinterpret_cast<char const*>(key.data()), ripple::uint256::size()};
 | 
						|
                blob.first = keyStr;
 | 
						|
                blob.second = std::to_string(ledgerSequence) + keyStr;
 | 
						|
            }
 | 
						|
            return res;
 | 
						|
        };
 | 
						|
        auto updateObjects = [](uint32_t ledgerSequence, auto objs) {
 | 
						|
            for (auto& [key, obj] : objs) {
 | 
						|
                obj = std::to_string(ledgerSequence) + obj;
 | 
						|
            }
 | 
						|
            return objs;
 | 
						|
        };
 | 
						|
        auto generateTxns = [](size_t numTxns, uint32_t ledgerSequence) {
 | 
						|
            std::vector<std::tuple<std::string, std::string, std::string>> res{numTxns};
 | 
						|
            ripple::uint256 base;
 | 
						|
            base = ledgerSequence * 100000ul;
 | 
						|
            for (auto& blob : res) {
 | 
						|
                ++base;
 | 
						|
                std::string const hashStr{reinterpret_cast<char const*>(base.data()), ripple::uint256::size()};
 | 
						|
                std::string const txnStr = "tx" + std::to_string(ledgerSequence) + hashStr;
 | 
						|
                std::string const metaStr = "meta" + std::to_string(ledgerSequence) + hashStr;
 | 
						|
                blob = std::make_tuple(hashStr, txnStr, metaStr);
 | 
						|
            }
 | 
						|
            return res;
 | 
						|
        };
 | 
						|
        auto generateAccounts = [](uint32_t ledgerSequence, uint32_t numAccounts) {
 | 
						|
            std::vector<ripple::AccountID> accounts;
 | 
						|
            ripple::AccountID base;
 | 
						|
            base = ledgerSequence * 998765ul;
 | 
						|
            for (size_t i = 0; i < numAccounts; ++i) {
 | 
						|
                ++base;
 | 
						|
                accounts.push_back(base);
 | 
						|
            }
 | 
						|
            return accounts;
 | 
						|
        };
 | 
						|
        auto generateAccountTx = [&](uint32_t ledgerSequence, auto txns) {
 | 
						|
            std::vector<AccountTransactionsData> ret;
 | 
						|
            util::MTRandomGenerator randomGenerator;
 | 
						|
            auto accounts = generateAccounts(ledgerSequence, 10);
 | 
						|
            uint32_t idx = 0;
 | 
						|
            for (auto& [hash, txn, meta] : txns) {
 | 
						|
                AccountTransactionsData data;
 | 
						|
                data.ledgerSequence = ledgerSequence;
 | 
						|
                data.transactionIndex = idx;
 | 
						|
                data.txHash = hash;
 | 
						|
                for (size_t i = 0; i < 3; ++i) {
 | 
						|
                    data.accounts.insert(accounts[randomGenerator.uniform(0ul, accounts.size() - 1)]);
 | 
						|
                }
 | 
						|
                ++idx;
 | 
						|
                ret.push_back(data);
 | 
						|
            }
 | 
						|
            return ret;
 | 
						|
        };
 | 
						|
        auto generateNextLedger = [this](auto lgrInfo) {
 | 
						|
            ++lgrInfo.seq;
 | 
						|
            lgrInfo.parentHash = lgrInfo.hash;
 | 
						|
            std::shuffle(lgrInfo.txHash.begin(), lgrInfo.txHash.end(), randomEngine_);
 | 
						|
            std::shuffle(lgrInfo.accountHash.begin(), lgrInfo.accountHash.end(), randomEngine_);
 | 
						|
            std::shuffle(lgrInfo.hash.begin(), lgrInfo.hash.end(), randomEngine_);
 | 
						|
            return lgrInfo;
 | 
						|
        };
 | 
						|
        auto writeLedger = [&](auto lgrInfo, auto txns, auto objs, auto accountTx, auto state) {
 | 
						|
            backend_->startWrites();
 | 
						|
 | 
						|
            backend_->writeLedger(lgrInfo, ledgerHeaderToBinaryString(lgrInfo));
 | 
						|
            for (auto [hash, txn, meta] : txns) {
 | 
						|
                backend_->writeTransaction(
 | 
						|
                    std::move(hash),
 | 
						|
                    lgrInfo.seq,
 | 
						|
                    lgrInfo.closeTime.time_since_epoch().count(),
 | 
						|
                    std::move(txn),
 | 
						|
                    std::move(meta)
 | 
						|
                );
 | 
						|
            }
 | 
						|
            for (auto const& [key, obj] : objs) {
 | 
						|
                backend_->writeLedgerObject(std::string{key}, lgrInfo.seq, std::string{obj});
 | 
						|
            }
 | 
						|
            if (state.count(lgrInfo.seq - 1) == 0 ||
 | 
						|
                std::find_if(state[lgrInfo.seq - 1].begin(), state[lgrInfo.seq - 1].end(), [&](auto obj) {
 | 
						|
                    return obj.first == objs[0].first;
 | 
						|
                }) == state[lgrInfo.seq - 1].end()) {
 | 
						|
                for (size_t i = 0; i < objs.size(); ++i) {
 | 
						|
                    if (i + 1 < objs.size()) {
 | 
						|
                        backend_->writeSuccessor(
 | 
						|
                            std::string{objs[i].first}, lgrInfo.seq, std::string{objs[i + 1].first}
 | 
						|
                        );
 | 
						|
                    } else {
 | 
						|
                        backend_->writeSuccessor(
 | 
						|
                            std::string{objs[i].first}, lgrInfo.seq, uint256ToString(data::kLAST_KEY)
 | 
						|
                        );
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                if (state.contains(lgrInfo.seq - 1)) {
 | 
						|
                    backend_->writeSuccessor(
 | 
						|
                        std::string{state[lgrInfo.seq - 1].back().first}, lgrInfo.seq, std::string{objs[0].first}
 | 
						|
                    );
 | 
						|
                } else {
 | 
						|
                    backend_->writeSuccessor(
 | 
						|
                        uint256ToString(data::kFIRST_KEY), lgrInfo.seq, std::string{objs[0].first}
 | 
						|
                    );
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            backend_->writeAccountTransactions(std::move(accountTx));
 | 
						|
            ASSERT_TRUE(backend_->finishWrites(lgrInfo.seq));
 | 
						|
        };
 | 
						|
 | 
						|
        auto checkLedger = [&](auto lgrInfo, auto txns, auto objs, auto accountTx) {
 | 
						|
            auto rng = backend_->fetchLedgerRange();
 | 
						|
            auto seq = lgrInfo.seq;
 | 
						|
            EXPECT_TRUE(rng);
 | 
						|
            EXPECT_EQ(rng->minSequence, lgrInfoOld.seq);
 | 
						|
            EXPECT_GE(rng->maxSequence, seq);
 | 
						|
            auto retLgr = backend_->fetchLedgerBySequence(seq, yield);
 | 
						|
            EXPECT_TRUE(retLgr);
 | 
						|
            EXPECT_EQ(ledgerHeaderToBlob(*retLgr), ledgerHeaderToBlob(lgrInfo));
 | 
						|
            auto retTxns = backend_->fetchAllTransactionsInLedger(seq, yield);
 | 
						|
            for (auto [hash, txn, meta] : txns) {
 | 
						|
                bool found = false;
 | 
						|
                for (auto [retTxn, retMeta, retSeq, retDate] : retTxns) {
 | 
						|
                    if (std::strncmp(
 | 
						|
                            reinterpret_cast<char const*>(retTxn.data()),
 | 
						|
                            static_cast<char const*>(txn.data()),
 | 
						|
                            txn.size()
 | 
						|
                        ) == 0 &&
 | 
						|
                        std::strncmp(
 | 
						|
                            reinterpret_cast<char const*>(retMeta.data()),
 | 
						|
                            static_cast<char const*>(meta.data()),
 | 
						|
                            meta.size()
 | 
						|
                        ) == 0)
 | 
						|
                        found = true;
 | 
						|
                }
 | 
						|
                ASSERT_TRUE(found);
 | 
						|
            }
 | 
						|
            for (auto [account, data] : accountTx) {
 | 
						|
                std::vector<data::TransactionAndMetadata> retData;
 | 
						|
                std::optional<data::TransactionsCursor> cursor;
 | 
						|
                do {
 | 
						|
                    uint32_t const limit = 10;
 | 
						|
                    auto [accountTransactions, retCursor] =
 | 
						|
                        backend_->fetchAccountTransactions(account, limit, false, cursor, yield);
 | 
						|
                    if (retCursor)
 | 
						|
                        EXPECT_EQ(accountTransactions.size(), limit);
 | 
						|
                    retData.insert(retData.end(), accountTransactions.begin(), accountTransactions.end());
 | 
						|
                    cursor = retCursor;
 | 
						|
                } while (cursor);
 | 
						|
                EXPECT_EQ(retData.size(), data.size());
 | 
						|
                for (size_t i = 0; i < retData.size(); ++i) {
 | 
						|
                    auto [txn, meta, _, _2] = retData[i];
 | 
						|
                    auto [_3, expTxn, expMeta] = data[i];
 | 
						|
                    EXPECT_STREQ(reinterpret_cast<char const*>(txn.data()), static_cast<char const*>(expTxn.data()));
 | 
						|
                    EXPECT_STREQ(reinterpret_cast<char const*>(meta.data()), static_cast<char const*>(expMeta.data()));
 | 
						|
                }
 | 
						|
            }
 | 
						|
            std::vector<ripple::uint256> keys;
 | 
						|
            for (auto [key, obj] : objs) {
 | 
						|
                auto retObj = backend_->fetchLedgerObject(binaryStringToUint256(key), seq, yield);
 | 
						|
                if (obj.size()) {
 | 
						|
                    ASSERT_TRUE(retObj.has_value());
 | 
						|
                    EXPECT_STREQ(static_cast<char const*>(obj.data()), reinterpret_cast<char const*>(retObj->data()));
 | 
						|
                } else {
 | 
						|
                    ASSERT_FALSE(retObj.has_value());
 | 
						|
                }
 | 
						|
                keys.push_back(binaryStringToUint256(key));
 | 
						|
            }
 | 
						|
 | 
						|
            {
 | 
						|
                auto retObjs = backend_->fetchLedgerObjects(keys, seq, yield);
 | 
						|
                ASSERT_EQ(retObjs.size(), objs.size());
 | 
						|
 | 
						|
                for (size_t i = 0; i < keys.size(); ++i) {
 | 
						|
                    auto [key, obj] = objs[i];
 | 
						|
                    auto retObj = retObjs[i];
 | 
						|
                    if (obj.size()) {
 | 
						|
                        ASSERT_TRUE(retObj.size());
 | 
						|
                        EXPECT_STREQ(
 | 
						|
                            static_cast<char const*>(obj.data()), reinterpret_cast<char const*>(retObj.data())
 | 
						|
                        );
 | 
						|
                    } else {
 | 
						|
                        ASSERT_FALSE(retObj.size());
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            data::LedgerPage page;
 | 
						|
            std::vector<data::LedgerObject> retObjs;
 | 
						|
            do {
 | 
						|
                uint32_t const limit = 10;
 | 
						|
                page = backend_->fetchLedgerPage(page.cursor, seq, limit, false, yield);
 | 
						|
                retObjs.insert(retObjs.end(), page.objects.begin(), page.objects.end());
 | 
						|
            } while (page.cursor);
 | 
						|
 | 
						|
            for (auto const& obj : objs) {
 | 
						|
                bool found = false;
 | 
						|
                for (auto const& retObj : retObjs) {
 | 
						|
                    if (ripple::strHex(obj.first) == ripple::strHex(retObj.key)) {
 | 
						|
                        found = true;
 | 
						|
                        ASSERT_EQ(ripple::strHex(obj.second), ripple::strHex(retObj.blob));
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                if (found != (obj.second.size() != 0))
 | 
						|
                    ASSERT_EQ(found, obj.second.size() != 0);
 | 
						|
            }
 | 
						|
        };
 | 
						|
 | 
						|
        std::map<uint32_t, std::vector<std::pair<std::string, std::string>>> state;
 | 
						|
        std::map<uint32_t, std::vector<std::tuple<std::string, std::string, std::string>>> allTxns;
 | 
						|
        std::unordered_map<std::string, std::pair<std::string, std::string>> allTxnsMap;
 | 
						|
        std::map<uint32_t, std::map<ripple::AccountID, std::vector<std::string>>> allAccountTx;
 | 
						|
        std::map<uint32_t, ripple::LedgerHeader> lgrInfos;
 | 
						|
        for (size_t i = 0; i < 10; ++i) {
 | 
						|
            lgrInfoNext = generateNextLedger(lgrInfoNext);
 | 
						|
            auto objs = generateObjects(25, lgrInfoNext.seq);
 | 
						|
            auto txns = generateTxns(10, lgrInfoNext.seq);
 | 
						|
            auto accountTx = generateAccountTx(lgrInfoNext.seq, txns);
 | 
						|
            for (auto rec : accountTx) {
 | 
						|
                for (auto account : rec.accounts) {
 | 
						|
                    allAccountTx[lgrInfoNext.seq][account].emplace_back(
 | 
						|
                        reinterpret_cast<char const*>(rec.txHash.data()), ripple::uint256::size()
 | 
						|
                    );
 | 
						|
                }
 | 
						|
            }
 | 
						|
            EXPECT_EQ(objs.size(), 25);
 | 
						|
            EXPECT_NE(objs[0], objs[1]);
 | 
						|
            EXPECT_EQ(txns.size(), 10);
 | 
						|
            EXPECT_NE(txns[0], txns[1]);
 | 
						|
            std::ranges::sort(objs);
 | 
						|
            state[lgrInfoNext.seq] = objs;
 | 
						|
            writeLedger(lgrInfoNext, txns, objs, accountTx, state);
 | 
						|
            allTxns[lgrInfoNext.seq] = txns;
 | 
						|
            lgrInfos[lgrInfoNext.seq] = lgrInfoNext;
 | 
						|
            for (auto& [hash, txn, meta] : txns) {
 | 
						|
                allTxnsMap[hash] = std::make_pair(txn, meta);
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        std::vector<std::pair<std::string, std::string>> objs;
 | 
						|
        for (size_t i = 0; i < 10; ++i) {
 | 
						|
            lgrInfoNext = generateNextLedger(lgrInfoNext);
 | 
						|
            if (objs.empty()) {
 | 
						|
                objs = generateObjects(25, lgrInfoNext.seq);
 | 
						|
            } else {
 | 
						|
                objs = updateObjects(lgrInfoNext.seq, objs);
 | 
						|
            }
 | 
						|
            auto txns = generateTxns(10, lgrInfoNext.seq);
 | 
						|
            auto accountTx = generateAccountTx(lgrInfoNext.seq, txns);
 | 
						|
            for (auto rec : accountTx) {
 | 
						|
                for (auto account : rec.accounts) {
 | 
						|
                    allAccountTx[lgrInfoNext.seq][account].emplace_back(
 | 
						|
                        reinterpret_cast<char const*>(rec.txHash.data()), ripple::uint256::size()
 | 
						|
                    );
 | 
						|
                }
 | 
						|
            }
 | 
						|
            EXPECT_EQ(objs.size(), 25);
 | 
						|
            EXPECT_NE(objs[0], objs[1]);
 | 
						|
            EXPECT_EQ(txns.size(), 10);
 | 
						|
            EXPECT_NE(txns[0], txns[1]);
 | 
						|
            std::ranges::sort(objs);
 | 
						|
            state[lgrInfoNext.seq] = objs;
 | 
						|
            writeLedger(lgrInfoNext, txns, objs, accountTx, state);
 | 
						|
            allTxns[lgrInfoNext.seq] = txns;
 | 
						|
            lgrInfos[lgrInfoNext.seq] = lgrInfoNext;
 | 
						|
            for (auto& [hash, txn, meta] : txns) {
 | 
						|
                allTxnsMap[hash] = std::make_pair(txn, meta);
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        auto flatten = [&](uint32_t max) {
 | 
						|
            std::vector<std::pair<std::string, std::string>> flat;
 | 
						|
            std::map<std::string, std::string> objs;
 | 
						|
            for (auto const& [seq, diff] : state) {
 | 
						|
                for (auto const& [k, v] : diff) {
 | 
						|
                    if (seq > max) {
 | 
						|
                        if (!objs.contains(k))
 | 
						|
                            objs[k] = "";
 | 
						|
                    } else {
 | 
						|
                        objs[k] = v;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
            flat.reserve(objs.size());
 | 
						|
            for (auto const& [key, value] : objs) {
 | 
						|
                flat.emplace_back(key, value);
 | 
						|
            }
 | 
						|
            return flat;
 | 
						|
        };
 | 
						|
 | 
						|
        auto flattenAccountTx = [&](uint32_t max) {
 | 
						|
            std::unordered_map<ripple::AccountID, std::vector<std::tuple<std::string, std::string, std::string>>>
 | 
						|
                accountTx;
 | 
						|
            for (auto const& [seq, map] : allAccountTx) {
 | 
						|
                if (seq > max)
 | 
						|
                    break;
 | 
						|
                for (auto& [account, hashes] : map) {
 | 
						|
                    for (auto& hash : hashes) {
 | 
						|
                        auto& [txn, meta] = allTxnsMap[hash];
 | 
						|
                        accountTx[account].emplace_back(hash, txn, meta);
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
            for (auto& [account, data] : accountTx)
 | 
						|
                std::ranges::reverse(data);
 | 
						|
            return accountTx;
 | 
						|
        };
 | 
						|
 | 
						|
        for (auto const& [seq, diff] : state) {
 | 
						|
            auto flat = flatten(seq);
 | 
						|
            checkLedger(lgrInfos[seq], allTxns[seq], flat, flattenAccountTx(seq));
 | 
						|
        }
 | 
						|
 | 
						|
        done = true;
 | 
						|
        work.reset();
 | 
						|
    });
 | 
						|
 | 
						|
    ctx_.run();
 | 
						|
    ASSERT_EQ(done, true);
 | 
						|
}
 | 
						|
 | 
						|
TEST_F(BackendCassandraTest, CacheIntegration)
 | 
						|
{
 | 
						|
    std::atomic_bool done = false;
 | 
						|
    std::optional<boost::asio::io_context::work> work;
 | 
						|
    work.emplace(ctx_);
 | 
						|
 | 
						|
    boost::asio::spawn(ctx_, [this, &done, &work](boost::asio::yield_context yield) {
 | 
						|
        backend_->cache().setFull();
 | 
						|
 | 
						|
        // this account is not related to the above transaction and
 | 
						|
        // metadata
 | 
						|
        std::string const accountHex =
 | 
						|
            "1100612200000000240480FDBC2503CE1A872D0000000555516931B2AD018EFFBE"
 | 
						|
            "17C5C9DCCF872F36837C2C6136ACF80F2A24079CF81FD0624000000005FF0E0781"
 | 
						|
            "142252F328CF91263417762570D67220CCB33B1370";
 | 
						|
        std::string const accountIndexHex = "E0311EB450B6177F969B94DBDDA83E99B7A0576ACD9079573876F16C0C004F06";
 | 
						|
 | 
						|
        std::string rawHeaderBlob = hexStringToBinaryString(kRAWHEADER);
 | 
						|
        std::string accountBlob = hexStringToBinaryString(accountHex);
 | 
						|
        std::string const accountIndexBlob = hexStringToBinaryString(accountIndexHex);
 | 
						|
        ripple::LedgerHeader const lgrInfo = util::deserializeHeader(ripple::makeSlice(rawHeaderBlob));
 | 
						|
 | 
						|
        backend_->startWrites();
 | 
						|
        backend_->writeLedger(lgrInfo, std::move(rawHeaderBlob));
 | 
						|
        backend_->writeSuccessor(uint256ToString(data::kFIRST_KEY), lgrInfo.seq, uint256ToString(data::kLAST_KEY));
 | 
						|
        ASSERT_TRUE(backend_->finishWrites(lgrInfo.seq));
 | 
						|
        {
 | 
						|
            auto rng = backend_->fetchLedgerRange();
 | 
						|
            EXPECT_TRUE(rng.has_value());
 | 
						|
            EXPECT_EQ(rng->minSequence, rng->maxSequence);
 | 
						|
            EXPECT_EQ(rng->maxSequence, lgrInfo.seq);
 | 
						|
        }
 | 
						|
        {
 | 
						|
            auto seq = backend_->fetchLatestLedgerSequence(yield);
 | 
						|
            EXPECT_TRUE(seq.has_value());
 | 
						|
            EXPECT_EQ(*seq, lgrInfo.seq);
 | 
						|
        }
 | 
						|
        {
 | 
						|
            auto retLgr = backend_->fetchLedgerBySequence(lgrInfo.seq, yield);
 | 
						|
            ASSERT_TRUE(retLgr.has_value());
 | 
						|
            EXPECT_EQ(retLgr->seq, lgrInfo.seq);
 | 
						|
            EXPECT_EQ(ledgerHeaderToBlob(lgrInfo), ledgerHeaderToBlob(*retLgr));
 | 
						|
        }
 | 
						|
        EXPECT_FALSE(backend_->fetchLedgerBySequence(lgrInfo.seq + 1, yield).has_value());
 | 
						|
        auto lgrInfoOld = lgrInfo;
 | 
						|
 | 
						|
        auto lgrInfoNext = lgrInfo;
 | 
						|
        lgrInfoNext.seq = lgrInfo.seq + 1;
 | 
						|
        lgrInfoNext.parentHash = lgrInfo.hash;
 | 
						|
        lgrInfoNext.hash++;
 | 
						|
        lgrInfoNext.accountHash = ~lgrInfo.accountHash;
 | 
						|
        {
 | 
						|
            std::string infoBlob = ledgerHeaderToBinaryString(lgrInfoNext);
 | 
						|
 | 
						|
            backend_->startWrites();
 | 
						|
            backend_->writeLedger(lgrInfoNext, std::move(infoBlob));
 | 
						|
            ASSERT_TRUE(backend_->finishWrites(lgrInfoNext.seq));
 | 
						|
        }
 | 
						|
        {
 | 
						|
            auto rng = backend_->fetchLedgerRange();
 | 
						|
            EXPECT_TRUE(rng.has_value());
 | 
						|
            EXPECT_EQ(rng->minSequence, lgrInfoOld.seq);
 | 
						|
            EXPECT_EQ(rng->maxSequence, lgrInfoNext.seq);
 | 
						|
        }
 | 
						|
        {
 | 
						|
            auto seq = backend_->fetchLatestLedgerSequence(yield);
 | 
						|
            EXPECT_EQ(seq, lgrInfoNext.seq);
 | 
						|
        }
 | 
						|
        {
 | 
						|
            auto retLgr = backend_->fetchLedgerBySequence(lgrInfoNext.seq, yield);
 | 
						|
            EXPECT_TRUE(retLgr.has_value());
 | 
						|
            EXPECT_EQ(retLgr->seq, lgrInfoNext.seq);
 | 
						|
            EXPECT_EQ(ledgerHeaderToBlob(*retLgr), ledgerHeaderToBlob(lgrInfoNext));
 | 
						|
            EXPECT_NE(ledgerHeaderToBlob(*retLgr), ledgerHeaderToBlob(lgrInfoOld));
 | 
						|
            retLgr = backend_->fetchLedgerBySequence(lgrInfoNext.seq - 1, yield);
 | 
						|
            EXPECT_EQ(ledgerHeaderToBlob(*retLgr), ledgerHeaderToBlob(lgrInfoOld));
 | 
						|
 | 
						|
            EXPECT_NE(ledgerHeaderToBlob(*retLgr), ledgerHeaderToBlob(lgrInfoNext));
 | 
						|
            retLgr = backend_->fetchLedgerBySequence(lgrInfoNext.seq - 2, yield);
 | 
						|
            EXPECT_FALSE(backend_->fetchLedgerBySequence(lgrInfoNext.seq - 2, yield).has_value());
 | 
						|
 | 
						|
            auto txns = backend_->fetchAllTransactionsInLedger(lgrInfoNext.seq, yield);
 | 
						|
            EXPECT_EQ(txns.size(), 0);
 | 
						|
            auto hashes = backend_->fetchAllTransactionHashesInLedger(lgrInfoNext.seq, yield);
 | 
						|
            EXPECT_EQ(hashes.size(), 0);
 | 
						|
        }
 | 
						|
        {
 | 
						|
            backend_->startWrites();
 | 
						|
            lgrInfoNext.seq = lgrInfoNext.seq + 1;
 | 
						|
            lgrInfoNext.txHash = ~lgrInfo.txHash;
 | 
						|
            lgrInfoNext.accountHash = lgrInfoNext.accountHash ^ lgrInfoNext.txHash;
 | 
						|
            lgrInfoNext.parentHash = lgrInfoNext.hash;
 | 
						|
            lgrInfoNext.hash++;
 | 
						|
 | 
						|
            backend_->writeLedger(lgrInfoNext, ledgerHeaderToBinaryString(lgrInfoNext));
 | 
						|
            backend_->writeLedgerObject(std::string{accountIndexBlob}, lgrInfoNext.seq, std::string{accountBlob});
 | 
						|
            auto key = ripple::uint256::fromVoidChecked(accountIndexBlob);
 | 
						|
            backend_->cache().update(
 | 
						|
                {{.key = *key, .blob = {accountBlob.begin(), accountBlob.end()}}}, lgrInfoNext.seq
 | 
						|
            );
 | 
						|
            backend_->writeSuccessor(uint256ToString(data::kFIRST_KEY), lgrInfoNext.seq, std::string{accountIndexBlob});
 | 
						|
            backend_->writeSuccessor(std::string{accountIndexBlob}, lgrInfoNext.seq, uint256ToString(data::kLAST_KEY));
 | 
						|
 | 
						|
            ASSERT_TRUE(backend_->finishWrites(lgrInfoNext.seq));
 | 
						|
        }
 | 
						|
        {
 | 
						|
            auto rng = backend_->fetchLedgerRange();
 | 
						|
            EXPECT_TRUE(rng);
 | 
						|
            EXPECT_EQ(rng->minSequence, lgrInfoOld.seq);
 | 
						|
            EXPECT_EQ(rng->maxSequence, lgrInfoNext.seq);
 | 
						|
            auto retLgr = backend_->fetchLedgerBySequence(lgrInfoNext.seq, yield);
 | 
						|
            EXPECT_TRUE(retLgr);
 | 
						|
            EXPECT_EQ(ledgerHeaderToBlob(*retLgr), ledgerHeaderToBlob(lgrInfoNext));
 | 
						|
            ripple::uint256 key256;
 | 
						|
            EXPECT_TRUE(key256.parseHex(accountIndexHex));
 | 
						|
            auto obj = backend_->fetchLedgerObject(key256, lgrInfoNext.seq, yield);
 | 
						|
            EXPECT_TRUE(obj);
 | 
						|
            EXPECT_STREQ(reinterpret_cast<char const*>(obj->data()), static_cast<char const*>(accountBlob.data()));
 | 
						|
            obj = backend_->fetchLedgerObject(key256, lgrInfoNext.seq + 1, yield);
 | 
						|
            EXPECT_TRUE(obj);
 | 
						|
            EXPECT_STREQ(reinterpret_cast<char const*>(obj->data()), static_cast<char const*>(accountBlob.data()));
 | 
						|
            obj = backend_->fetchLedgerObject(key256, lgrInfoOld.seq - 1, yield);
 | 
						|
            EXPECT_FALSE(obj);
 | 
						|
        }
 | 
						|
        std::string accountBlobOld = accountBlob;
 | 
						|
        {
 | 
						|
            backend_->startWrites();
 | 
						|
            lgrInfoNext.seq = lgrInfoNext.seq + 1;
 | 
						|
            lgrInfoNext.parentHash = lgrInfoNext.hash;
 | 
						|
            lgrInfoNext.hash++;
 | 
						|
            lgrInfoNext.txHash = lgrInfoNext.txHash ^ lgrInfoNext.accountHash;
 | 
						|
            lgrInfoNext.accountHash = ~(lgrInfoNext.accountHash ^ lgrInfoNext.txHash);
 | 
						|
 | 
						|
            backend_->writeLedger(lgrInfoNext, ledgerHeaderToBinaryString(lgrInfoNext));
 | 
						|
            std::shuffle(accountBlob.begin(), accountBlob.end(), randomEngine_);
 | 
						|
            auto key = ripple::uint256::fromVoidChecked(accountIndexBlob);
 | 
						|
            backend_->cache().update(
 | 
						|
                {{.key = *key, .blob = {accountBlob.begin(), accountBlob.end()}}}, lgrInfoNext.seq
 | 
						|
            );
 | 
						|
            backend_->writeLedgerObject(std::string{accountIndexBlob}, lgrInfoNext.seq, std::string{accountBlob});
 | 
						|
 | 
						|
            ASSERT_TRUE(backend_->finishWrites(lgrInfoNext.seq));
 | 
						|
        }
 | 
						|
        {
 | 
						|
            auto rng = backend_->fetchLedgerRange();
 | 
						|
            EXPECT_TRUE(rng);
 | 
						|
            EXPECT_EQ(rng->minSequence, lgrInfoOld.seq);
 | 
						|
            EXPECT_EQ(rng->maxSequence, lgrInfoNext.seq);
 | 
						|
            auto retLgr = backend_->fetchLedgerBySequence(lgrInfoNext.seq, yield);
 | 
						|
            EXPECT_TRUE(retLgr);
 | 
						|
 | 
						|
            ripple::uint256 key256;
 | 
						|
            EXPECT_TRUE(key256.parseHex(accountIndexHex));
 | 
						|
            auto obj = backend_->fetchLedgerObject(key256, lgrInfoNext.seq, yield);
 | 
						|
            EXPECT_TRUE(obj);
 | 
						|
            EXPECT_STREQ(reinterpret_cast<char const*>(obj->data()), static_cast<char const*>(accountBlob.data()));
 | 
						|
            obj = backend_->fetchLedgerObject(key256, lgrInfoNext.seq + 1, yield);
 | 
						|
            EXPECT_TRUE(obj);
 | 
						|
            EXPECT_STREQ(reinterpret_cast<char const*>(obj->data()), static_cast<char const*>(accountBlob.data()));
 | 
						|
            obj = backend_->fetchLedgerObject(key256, lgrInfoNext.seq - 1, yield);
 | 
						|
            EXPECT_TRUE(obj);
 | 
						|
            EXPECT_STREQ(reinterpret_cast<char const*>(obj->data()), static_cast<char const*>(accountBlobOld.data()));
 | 
						|
            obj = backend_->fetchLedgerObject(key256, lgrInfoOld.seq - 1, yield);
 | 
						|
            EXPECT_FALSE(obj);
 | 
						|
        }
 | 
						|
        {
 | 
						|
            backend_->startWrites();
 | 
						|
            lgrInfoNext.seq = lgrInfoNext.seq + 1;
 | 
						|
            lgrInfoNext.parentHash = lgrInfoNext.hash;
 | 
						|
            lgrInfoNext.hash++;
 | 
						|
            lgrInfoNext.txHash = lgrInfoNext.txHash ^ lgrInfoNext.accountHash;
 | 
						|
            lgrInfoNext.accountHash = ~(lgrInfoNext.accountHash ^ lgrInfoNext.txHash);
 | 
						|
 | 
						|
            backend_->writeLedger(lgrInfoNext, ledgerHeaderToBinaryString(lgrInfoNext));
 | 
						|
            auto key = ripple::uint256::fromVoidChecked(accountIndexBlob);
 | 
						|
            backend_->cache().update({{.key = *key, .blob = {}}}, lgrInfoNext.seq);
 | 
						|
            backend_->writeLedgerObject(std::string{accountIndexBlob}, lgrInfoNext.seq, std::string{});
 | 
						|
            backend_->writeSuccessor(
 | 
						|
                uint256ToString(data::kFIRST_KEY), lgrInfoNext.seq, uint256ToString(data::kLAST_KEY)
 | 
						|
            );
 | 
						|
 | 
						|
            ASSERT_TRUE(backend_->finishWrites(lgrInfoNext.seq));
 | 
						|
        }
 | 
						|
        {
 | 
						|
            auto rng = backend_->fetchLedgerRange();
 | 
						|
            EXPECT_TRUE(rng);
 | 
						|
            EXPECT_EQ(rng->minSequence, lgrInfoOld.seq);
 | 
						|
            EXPECT_EQ(rng->maxSequence, lgrInfoNext.seq);
 | 
						|
            auto retLgr = backend_->fetchLedgerBySequence(lgrInfoNext.seq, yield);
 | 
						|
            EXPECT_TRUE(retLgr);
 | 
						|
 | 
						|
            ripple::uint256 key256;
 | 
						|
            EXPECT_TRUE(key256.parseHex(accountIndexHex));
 | 
						|
            auto obj = backend_->fetchLedgerObject(key256, lgrInfoNext.seq, yield);
 | 
						|
            EXPECT_FALSE(obj);
 | 
						|
            obj = backend_->fetchLedgerObject(key256, lgrInfoNext.seq + 1, yield);
 | 
						|
            EXPECT_FALSE(obj);
 | 
						|
            obj = backend_->fetchLedgerObject(key256, lgrInfoNext.seq - 2, yield);
 | 
						|
            EXPECT_TRUE(obj);
 | 
						|
            EXPECT_STREQ(reinterpret_cast<char const*>(obj->data()), static_cast<char const*>(accountBlobOld.data()));
 | 
						|
            obj = backend_->fetchLedgerObject(key256, lgrInfoOld.seq - 1, yield);
 | 
						|
            EXPECT_FALSE(obj);
 | 
						|
        }
 | 
						|
 | 
						|
        auto generateObjects = [](size_t numObjects, uint64_t ledgerSequence) {
 | 
						|
            std::vector<std::pair<std::string, std::string>> res{numObjects};
 | 
						|
            ripple::uint256 key;
 | 
						|
            key = ledgerSequence * 100000;
 | 
						|
 | 
						|
            for (auto& blob : res) {
 | 
						|
                ++key;
 | 
						|
                std::string const keyStr{reinterpret_cast<char const*>(key.data()), ripple::uint256::size()};
 | 
						|
                blob.first = keyStr;
 | 
						|
                blob.second = std::to_string(ledgerSequence) + keyStr;
 | 
						|
            }
 | 
						|
            return res;
 | 
						|
        };
 | 
						|
        auto updateObjects = [](uint32_t ledgerSequence, auto objs) {
 | 
						|
            for (auto& [key, obj] : objs) {
 | 
						|
                obj = std::to_string(ledgerSequence) + obj;
 | 
						|
            }
 | 
						|
            return objs;
 | 
						|
        };
 | 
						|
 | 
						|
        auto generateNextLedger = [this](auto lgrInfo) {
 | 
						|
            ++lgrInfo.seq;
 | 
						|
            lgrInfo.parentHash = lgrInfo.hash;
 | 
						|
            std::shuffle(lgrInfo.txHash.begin(), lgrInfo.txHash.end(), randomEngine_);
 | 
						|
            std::shuffle(lgrInfo.accountHash.begin(), lgrInfo.accountHash.end(), randomEngine_);
 | 
						|
            std::shuffle(lgrInfo.hash.begin(), lgrInfo.hash.end(), randomEngine_);
 | 
						|
            return lgrInfo;
 | 
						|
        };
 | 
						|
        auto writeLedger = [&](auto lgrInfo, auto objs, auto state) {
 | 
						|
            backend_->startWrites();
 | 
						|
 | 
						|
            backend_->writeLedger(lgrInfo, std::move(ledgerHeaderToBinaryString(lgrInfo)));
 | 
						|
            std::vector<data::LedgerObject> cacheUpdates;
 | 
						|
            for (auto [key, obj] : objs) {
 | 
						|
                backend_->writeLedgerObject(std::string{key}, lgrInfo.seq, std::string{obj});
 | 
						|
                auto key256 = ripple::uint256::fromVoidChecked(key);
 | 
						|
                cacheUpdates.push_back({*key256, {obj.begin(), obj.end()}});
 | 
						|
            }
 | 
						|
            backend_->cache().update(cacheUpdates, lgrInfo.seq);
 | 
						|
            if (state.count(lgrInfo.seq - 1) == 0 ||
 | 
						|
                std::find_if(state[lgrInfo.seq - 1].begin(), state[lgrInfo.seq - 1].end(), [&](auto obj) {
 | 
						|
                    return obj.first == objs[0].first;
 | 
						|
                }) == state[lgrInfo.seq - 1].end()) {
 | 
						|
                for (size_t i = 0; i < objs.size(); ++i) {
 | 
						|
                    if (i + 1 < objs.size()) {
 | 
						|
                        backend_->writeSuccessor(
 | 
						|
                            std::string{objs[i].first}, lgrInfo.seq, std::string{objs[i + 1].first}
 | 
						|
                        );
 | 
						|
                    } else {
 | 
						|
                        backend_->writeSuccessor(
 | 
						|
                            std::string{objs[i].first}, lgrInfo.seq, uint256ToString(data::kLAST_KEY)
 | 
						|
                        );
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                if (state.contains(lgrInfo.seq - 1)) {
 | 
						|
                    backend_->writeSuccessor(
 | 
						|
                        std::string{state[lgrInfo.seq - 1].back().first}, lgrInfo.seq, std::string{objs[0].first}
 | 
						|
                    );
 | 
						|
                } else {
 | 
						|
                    backend_->writeSuccessor(
 | 
						|
                        uint256ToString(data::kFIRST_KEY), lgrInfo.seq, std::string{objs[0].first}
 | 
						|
                    );
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            ASSERT_TRUE(backend_->finishWrites(lgrInfo.seq));
 | 
						|
        };
 | 
						|
 | 
						|
        auto checkLedger = [&](auto lgrInfo, auto objs) {
 | 
						|
            auto rng = backend_->fetchLedgerRange();
 | 
						|
            auto seq = lgrInfo.seq;
 | 
						|
            EXPECT_TRUE(rng);
 | 
						|
            EXPECT_EQ(rng->minSequence, lgrInfoOld.seq);
 | 
						|
            EXPECT_GE(rng->maxSequence, seq);
 | 
						|
            auto retLgr = backend_->fetchLedgerBySequence(seq, yield);
 | 
						|
            EXPECT_TRUE(retLgr);
 | 
						|
            EXPECT_EQ(ledgerHeaderToBlob(*retLgr), ledgerHeaderToBlob(lgrInfo));
 | 
						|
            retLgr = backend_->fetchLedgerByHash(lgrInfo.hash, yield);
 | 
						|
            EXPECT_TRUE(retLgr);
 | 
						|
            EXPECT_EQ(ledgerHeaderToBlob(*retLgr), ledgerHeaderToBlob(lgrInfo))
 | 
						|
                << "retLgr seq:" << retLgr->seq << "; lgrInfo seq:" << lgrInfo.seq << "; retLgr hash:" << retLgr->hash
 | 
						|
                << "; lgrInfo hash:" << lgrInfo.hash << "; retLgr parentHash:" << retLgr->parentHash
 | 
						|
                << "; lgr Info parentHash:" << lgrInfo.parentHash;
 | 
						|
 | 
						|
            std::vector<ripple::uint256> keys;
 | 
						|
            for (auto [key, obj] : objs) {
 | 
						|
                auto retObj = backend_->fetchLedgerObject(binaryStringToUint256(key), seq, yield);
 | 
						|
                if (obj.size()) {
 | 
						|
                    ASSERT_TRUE(retObj.has_value());
 | 
						|
                    EXPECT_STREQ(static_cast<char const*>(obj.data()), reinterpret_cast<char const*>(retObj->data()));
 | 
						|
                } else {
 | 
						|
                    ASSERT_FALSE(retObj.has_value());
 | 
						|
                }
 | 
						|
                keys.push_back(binaryStringToUint256(key));
 | 
						|
            }
 | 
						|
 | 
						|
            {
 | 
						|
                auto retObjs = backend_->fetchLedgerObjects(keys, seq, yield);
 | 
						|
                ASSERT_EQ(retObjs.size(), objs.size());
 | 
						|
 | 
						|
                for (size_t i = 0; i < keys.size(); ++i) {
 | 
						|
                    auto [key, obj] = objs[i];
 | 
						|
                    auto retObj = retObjs[i];
 | 
						|
                    if (obj.size()) {
 | 
						|
                        ASSERT_TRUE(retObj.size());
 | 
						|
                        EXPECT_STREQ(
 | 
						|
                            static_cast<char const*>(obj.data()), reinterpret_cast<char const*>(retObj.data())
 | 
						|
                        );
 | 
						|
                    } else {
 | 
						|
                        ASSERT_FALSE(retObj.size());
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
            data::LedgerPage page;
 | 
						|
            std::vector<data::LedgerObject> retObjs;
 | 
						|
            do {
 | 
						|
                uint32_t const limit = 10;
 | 
						|
                page = backend_->fetchLedgerPage(page.cursor, seq, limit, false, yield);
 | 
						|
                retObjs.insert(retObjs.end(), page.objects.begin(), page.objects.end());
 | 
						|
            } while (page.cursor);
 | 
						|
            for (auto const& obj : objs) {
 | 
						|
                bool found = false;
 | 
						|
                for (auto const& retObj : retObjs) {
 | 
						|
                    if (ripple::strHex(obj.first) == ripple::strHex(retObj.key)) {
 | 
						|
                        found = true;
 | 
						|
                        ASSERT_EQ(ripple::strHex(obj.second), ripple::strHex(retObj.blob));
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                if (found != (obj.second.size() != 0))
 | 
						|
                    ASSERT_EQ(found, obj.second.size() != 0);
 | 
						|
            }
 | 
						|
        };
 | 
						|
 | 
						|
        std::map<uint32_t, std::vector<std::pair<std::string, std::string>>> state;
 | 
						|
        std::map<uint32_t, ripple::LedgerHeader> lgrInfos;
 | 
						|
        for (size_t i = 0; i < 10; ++i) {
 | 
						|
            lgrInfoNext = generateNextLedger(lgrInfoNext);
 | 
						|
            auto objs = generateObjects(25, lgrInfoNext.seq);
 | 
						|
            EXPECT_EQ(objs.size(), 25);
 | 
						|
            EXPECT_NE(objs[0], objs[1]);
 | 
						|
            std::ranges::sort(objs);
 | 
						|
            state[lgrInfoNext.seq] = objs;
 | 
						|
            writeLedger(lgrInfoNext, objs, state);
 | 
						|
            lgrInfos[lgrInfoNext.seq] = lgrInfoNext;
 | 
						|
        }
 | 
						|
 | 
						|
        std::vector<std::pair<std::string, std::string>> objs;
 | 
						|
        for (size_t i = 0; i < 10; ++i) {
 | 
						|
            lgrInfoNext = generateNextLedger(lgrInfoNext);
 | 
						|
            if (objs.empty()) {
 | 
						|
                objs = generateObjects(25, lgrInfoNext.seq);
 | 
						|
            } else {
 | 
						|
                objs = updateObjects(lgrInfoNext.seq, objs);
 | 
						|
            }
 | 
						|
            EXPECT_EQ(objs.size(), 25);
 | 
						|
            EXPECT_NE(objs[0], objs[1]);
 | 
						|
            std::ranges::sort(objs);
 | 
						|
            state[lgrInfoNext.seq] = objs;
 | 
						|
            writeLedger(lgrInfoNext, objs, state);
 | 
						|
            lgrInfos[lgrInfoNext.seq] = lgrInfoNext;
 | 
						|
        }
 | 
						|
 | 
						|
        auto flatten = [&](uint32_t max) {
 | 
						|
            std::vector<std::pair<std::string, std::string>> flat;
 | 
						|
            std::map<std::string, std::string> objs;
 | 
						|
            for (auto const& [seq, diff] : state) {
 | 
						|
                for (auto const& [k, v] : diff) {
 | 
						|
                    if (seq > max) {
 | 
						|
                        if (!objs.contains(k))
 | 
						|
                            objs[k] = "";
 | 
						|
                    } else {
 | 
						|
                        objs[k] = v;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
            flat.reserve(objs.size());
 | 
						|
            for (auto const& [key, value] : objs) {
 | 
						|
                flat.emplace_back(key, value);
 | 
						|
            }
 | 
						|
            return flat;
 | 
						|
        };
 | 
						|
 | 
						|
        for (auto const& [seq, diff] : state) {
 | 
						|
            auto flat = flatten(seq);
 | 
						|
            checkLedger(lgrInfos[seq], flat);
 | 
						|
        }
 | 
						|
 | 
						|
        done = true;
 | 
						|
        work.reset();
 | 
						|
    });
 | 
						|
 | 
						|
    ctx_.run();
 | 
						|
    ASSERT_EQ(done, true);
 | 
						|
}
 | 
						|
 | 
						|
class CacheBackendCassandraTest : public BackendCassandraTestBase {
 | 
						|
protected:
 | 
						|
    using TestBackendType = data::cassandra::BasicCassandraBackend<
 | 
						|
        SettingsProvider,
 | 
						|
        data::cassandra::impl::DefaultExecutionStrategy<>,
 | 
						|
        MockLedgerHeaderCache>;
 | 
						|
 | 
						|
    std::unique_ptr<BackendInterface> backend_{std::make_unique<TestBackendType>(settingsProvider_, cache_, false)};
 | 
						|
 | 
						|
public:
 | 
						|
    MockLedgerHeaderCache&
 | 
						|
    getMockCache()
 | 
						|
    {
 | 
						|
        return dynamic_cast<TestBackendType&>(*backend_).ledgerCache_;
 | 
						|
    }
 | 
						|
};
 | 
						|
 | 
						|
TEST_F(CacheBackendCassandraTest, CacheFetchLedgerBySeq)
 | 
						|
{
 | 
						|
    runSpawn([&](boost::asio::yield_context yield) {
 | 
						|
        auto rawHeaderBlob = hexStringToBinaryString(kRAWHEADER);
 | 
						|
        ripple::LedgerHeader const lgrInfo = util::deserializeHeader(ripple::makeSlice(rawHeaderBlob));
 | 
						|
 | 
						|
        backend_->writeLedger(lgrInfo, std::move(rawHeaderBlob));
 | 
						|
        auto const testLedgerSeq = lgrInfo.seq;
 | 
						|
        ASSERT_TRUE(backend_->finishWrites(lgrInfo.seq));
 | 
						|
 | 
						|
        EXPECT_CALL(getMockCache(), put(data::FetchLedgerCache::CacheEntry{lgrInfo, testLedgerSeq}));
 | 
						|
 | 
						|
        {
 | 
						|
            testing::InSequence const s;
 | 
						|
            // first time, getSeq doesn't match ledger sequence
 | 
						|
            EXPECT_CALL(getMockCache(), get()).WillOnce(testing::Return(std::nullopt));
 | 
						|
 | 
						|
            // second time, it would be cached
 | 
						|
            EXPECT_CALL(getMockCache(), get())
 | 
						|
                .WillOnce(testing::Return(data::FetchLedgerCache::CacheEntry{.ledger = lgrInfo, .seq = testLedgerSeq}));
 | 
						|
        }
 | 
						|
 | 
						|
        {
 | 
						|
            // backend should cache the result of fetchLedgerBySequence
 | 
						|
            auto const ledger = backend_->fetchLedgerBySequence(testLedgerSeq, yield);
 | 
						|
            ASSERT_TRUE(ledger.has_value());
 | 
						|
            EXPECT_EQ(ledger->seq, lgrInfo.seq);
 | 
						|
        }
 | 
						|
 | 
						|
        {
 | 
						|
            // Second call: should return from cache
 | 
						|
            auto const ledger = backend_->fetchLedgerBySequence(testLedgerSeq, yield);
 | 
						|
            ASSERT_TRUE(ledger.has_value());
 | 
						|
            EXPECT_EQ(ledger->seq, lgrInfo.seq);
 | 
						|
        }
 | 
						|
    });
 | 
						|
}
 | 
						|
 | 
						|
struct BackendCassandraNodeMessageTest : BackendCassandraTest {
 | 
						|
    boost::uuids::random_generator generateUuid{};
 | 
						|
};
 | 
						|
 | 
						|
TEST_F(BackendCassandraNodeMessageTest, UpdateFetch)
 | 
						|
{
 | 
						|
    static boost::uuids::uuid const kUUID = generateUuid();
 | 
						|
    static std::string const kMESSAGE = "some message";
 | 
						|
 | 
						|
    EXPECT_NO_THROW({ backend_->writeNodeMessage(kUUID, kMESSAGE); });
 | 
						|
 | 
						|
    runSpawn([&](boost::asio::yield_context yield) {
 | 
						|
        auto const readResult = backend_->fetchClioNodesData(yield);
 | 
						|
        ASSERT_TRUE(readResult) << readResult.error();
 | 
						|
        ASSERT_EQ(readResult->size(), 1);
 | 
						|
        auto const& [uuid, message] = (*readResult)[0];
 | 
						|
        EXPECT_EQ(uuid, kUUID);
 | 
						|
        EXPECT_EQ(message, kMESSAGE);
 | 
						|
    });
 | 
						|
}
 | 
						|
 | 
						|
TEST_F(BackendCassandraNodeMessageTest, UpdateFetchMultipleMessages)
 | 
						|
{
 | 
						|
    std::unordered_map<boost::uuids::uuid, std::string> kDATA = {
 | 
						|
        {generateUuid(), std::string{"some message"}},
 | 
						|
        {generateUuid(), std::string{"other message"}},
 | 
						|
        {generateUuid(), std::string{"message 3"}}
 | 
						|
    };
 | 
						|
 | 
						|
    EXPECT_NO_THROW({
 | 
						|
        for (auto const& [uuid, message] : kDATA) {
 | 
						|
            backend_->writeNodeMessage(uuid, message);
 | 
						|
        }
 | 
						|
    });
 | 
						|
 | 
						|
    runSpawn([&](boost::asio::yield_context yield) {
 | 
						|
        auto const readResult = backend_->fetchClioNodesData(yield);
 | 
						|
        ASSERT_TRUE(readResult) << readResult.error();
 | 
						|
        ASSERT_EQ(readResult->size(), kDATA.size());
 | 
						|
 | 
						|
        for (size_t i = 0; i < readResult->size(); ++i) {
 | 
						|
            auto const& [uuid, message] = (*readResult)[i];
 | 
						|
            auto const it = kDATA.find(uuid);
 | 
						|
            ASSERT_NE(it, kDATA.end()) << uuid << " not found";
 | 
						|
            EXPECT_EQ(it->second, message);
 | 
						|
        }
 | 
						|
    });
 | 
						|
}
 | 
						|
 | 
						|
TEST_F(BackendCassandraNodeMessageTest, MessageDisappearsAfterTTL)
 | 
						|
{
 | 
						|
    EXPECT_NO_THROW({ backend_->writeNodeMessage(generateUuid(), "some message"); });
 | 
						|
 | 
						|
    std::this_thread::sleep_for(std::chrono::milliseconds{2005});
 | 
						|
    runSpawn([&](boost::asio::yield_context yield) {
 | 
						|
        auto const readResult = backend_->fetchClioNodesData(yield);
 | 
						|
        ASSERT_TRUE(readResult) << readResult.error();
 | 
						|
        EXPECT_TRUE(readResult->empty());
 | 
						|
    });
 | 
						|
}
 | 
						|
 | 
						|
TEST_F(BackendCassandraNodeMessageTest, UpdatingMessageKeepsItAlive)
 | 
						|
{
 | 
						|
#if defined(__APPLE__)
 | 
						|
    GTEST_SKIP() << "Skipping test on Apple platform due to slow DB";
 | 
						|
#else
 | 
						|
    static boost::uuids::uuid const kUUID = generateUuid();
 | 
						|
    static std::string const kUPDATED_MESSAGE = "updated message";
 | 
						|
 | 
						|
    EXPECT_NO_THROW({ backend_->writeNodeMessage(kUUID, "some message"); });
 | 
						|
    std::this_thread::sleep_for(std::chrono::milliseconds{1000});
 | 
						|
 | 
						|
    EXPECT_NO_THROW({ backend_->writeNodeMessage(kUUID, kUPDATED_MESSAGE); });
 | 
						|
    std::this_thread::sleep_for(std::chrono::milliseconds{1005});
 | 
						|
 | 
						|
    runSpawn([&](boost::asio::yield_context yield) {
 | 
						|
        auto const readResult = backend_->fetchClioNodesData(yield);
 | 
						|
        ASSERT_TRUE(readResult) << readResult.error();
 | 
						|
        ASSERT_EQ(readResult->size(), 1);
 | 
						|
        auto const& [uuid, message] = (*readResult)[0];
 | 
						|
        EXPECT_EQ(uuid, kUUID);
 | 
						|
        EXPECT_EQ(message, kUPDATED_MESSAGE);
 | 
						|
    });
 | 
						|
#endif
 | 
						|
}
 |