1#include <xrpl/peerfinder/detail/Livecache.h>
3#include <xrpl/basics/chrono.h>
4#include <xrpl/basics/random.h>
5#include <xrpl/beast/net/IPAddressV4.h>
6#include <xrpl/beast/net/IPAddressV6.h>
7#include <xrpl/beast/net/IPEndpoint.h>
8#include <xrpl/beast/utility/Journal.h>
9#include <xrpl/beast/utility/PropertyStream.h>
10#include <xrpl/json/JsonPropertyStream.h>
11#include <xrpl/json/json_forwards.h>
12#include <xrpl/json/json_value.h>
13#include <xrpl/peerfinder/Types.h>
14#include <xrpl/peerfinder/detail/Tuning.h>
16#include <boost/algorithm/string/classification.hpp>
17#include <boost/algorithm/string/split.hpp>
18#include <boost/algorithm/string/trim.hpp>
19#include <boost/lexical_cast.hpp>
21#include <gtest/gtest.h>
22#include <helpers/TestSink.h>
35class LivecacheTest :
public ::testing::Test
44 static beast::ip::Endpoint
45 endpoint(std::uint16_t index,
bool v4 =
true)
47 auto const port =
static_cast<std::uint16_t
>(10000 + index);
51 auto bytes = beast::ip::AddressV4::bytes_type{
53 static_cast<std::uint8_t
>((index / 256) % 256),
54 static_cast<std::uint8_t
>(index % 256),
59 auto bytes = beast::ip::AddressV6::bytes_type{
73 static_cast<std::uint8_t
>((index / 256) % 256),
74 static_cast<std::uint8_t
>(index % 256),
80 addEndpoint(beast::ip::Endpoint
const& ep, std::uint32_t hops = 0)
82 cache_.insert(Endpoint{ep, hops});
86 Livecache<> cache_{clock_, journal()};
93 EXPECT_TRUE(cache_.empty());
95 for (
auto i = 0; i < 10; ++i)
96 addEndpoint(endpoint(i,
true));
98 EXPECT_FALSE(cache_.empty());
99 EXPECT_EQ(cache_.size(), 10u);
101 for (
auto i = 10; i < 20; ++i)
102 addEndpoint(endpoint(i,
false));
104 EXPECT_FALSE(cache_.empty());
105 EXPECT_EQ(cache_.size(), 20u);
108TEST_F(LivecacheTest, insert_update_keeps_lowest_hop_count)
110 auto const ep1 =
Endpoint{endpoint(1), 2};
112 ASSERT_EQ(cache_.size(), 1u);
113 EXPECT_EQ((cache_.hops.begin() + 2)->begin()->hops, 2u);
115 auto const ep2 =
Endpoint{ep1.address, 4};
117 EXPECT_EQ(cache_.size(), 1u);
118 EXPECT_EQ((cache_.hops.begin() + 2)->begin()->hops, 2u);
120 auto const ep3 =
Endpoint{ep1.address, 2};
122 EXPECT_EQ(cache_.size(), 1u);
123 EXPECT_EQ((cache_.hops.begin() + 2)->begin()->hops, 2u);
125 auto const ep4 =
Endpoint{ep1.address, 1};
127 EXPECT_EQ(cache_.size(), 1u);
128 EXPECT_EQ((cache_.hops.begin() + 1)->begin()->hops, 1u);
131TEST_F(LivecacheTest, hop_iterators_support_const_reverse_and_move_back)
133 auto const ep1 =
Endpoint{endpoint(1), 1};
134 auto const ep2 =
Endpoint{endpoint(2), 1};
138 auto hop = *(cache_.hops.begin() + 1);
139 ASSERT_NE(hop.begin(), hop.end());
140 ASSERT_NE(hop.cbegin(), hop.cend());
141 ASSERT_NE(hop.rbegin(), hop.rend());
142 ASSERT_NE(hop.crbegin(), hop.crend());
144 auto const firstAddress = hop.begin()->address;
145 hop.moveBack(hop.begin());
146 EXPECT_EQ(hop.rbegin()->address, firstAddress);
148 auto const& constHops = cache_.hops;
149 EXPECT_NE(constHops.begin(), constHops.end());
150 EXPECT_NE(constHops.cbegin(), constHops.cend());
151 EXPECT_NE(constHops.rbegin(), constHops.rend());
152 EXPECT_NE(constHops.crbegin(), constHops.crend());
154 auto const constHop = *(constHops.cbegin() + 1);
155 EXPECT_EQ(
std::distance(constHop.begin(), constHop.end()), 2);
156 EXPECT_EQ(
std::distance(constHop.cbegin(), constHop.cend()), 2);
157 EXPECT_EQ(
std::distance(constHop.rbegin(), constHop.rend()), 2);
158 EXPECT_EQ(
std::distance(constHop.crbegin(), constHop.crend()), 2);
161TEST_F(LivecacheTest, on_write_reports_entries_and_expiration)
163 cache_.insert(
Endpoint{endpoint(1), 1});
172 auto const& top = stream.top();
173 EXPECT_EQ(top[
"size"].asUInt(), 2u);
174 EXPECT_FALSE(top[
"hist"].asString().empty());
175 ASSERT_TRUE(top.isMember(
"entries"));
176 ASSERT_EQ(top[
"entries"].size(), 2u);
177 auto const& entry = top[
"entries"][
json::UInt{0}];
178 EXPECT_TRUE(entry.
isMember(
"hops"));
179 EXPECT_TRUE(entry.
isMember(
"address"));
180 EXPECT_TRUE(entry.
isMember(
"expires"));
183TEST_F(LivecacheTest, expire_removes_entries_after_ttl)
185 using namespace std::chrono_literals;
187 cache_.insert(
Endpoint{endpoint(1), 1});
188 ASSERT_EQ(cache_.size(), 1u);
191 EXPECT_EQ(cache_.size(), 1u);
195 EXPECT_EQ(cache_.size(), 1u);
199 EXPECT_TRUE(cache_.empty());
202TEST_F(LivecacheTest, expire_removes_multiple_entries_after_ttl)
204 using namespace std::chrono_literals;
206 cache_.insert(
Endpoint{endpoint(1), 1});
207 cache_.insert(
Endpoint{endpoint(2), 2});
211 EXPECT_TRUE(cache_.empty());
214TEST_F(LivecacheTest, histogram_counts_all_entries)
216 constexpr auto kNumEndpoints = 40;
218 for (
auto i = 0; i < kNumEndpoints; ++i)
223 auto const histogram = cache_.hops.histogram();
224 ASSERT_FALSE(histogram.empty());
227 boost::split(values, histogram, boost::algorithm::is_any_of(
","));
230 for (
auto const& value : values)
232 auto const count = boost::lexical_cast<int>(boost::trim_copy(value));
236 EXPECT_EQ(
sum, kNumEndpoints);
239TEST_F(LivecacheTest, shuffle_preserves_bucket_contents)
241 for (
auto i = 0; i < 100; ++i)
250 return rhs.hops < lhs.
hops || (rhs.hops == lhs.
hops && rhs.address < lhs.
address);
255 auto const sameEndpoints =
257 return lhs.size() == rhs.size() &&
258 std::equal(lhs.begin(), lhs.end(), rhs.begin(), sameEndpoint);
262 AllHops beforeSorted;
263 for (
auto i =
std::make_pair(0, cache_.hops.begin()); i.second != cache_.hops.end();
264 ++i.first, ++i.second)
271 cache_.hops.shuffle();
275 for (
auto i =
std::make_pair(0, cache_.hops.begin()); i.second != cache_.hops.end();
276 ++i.first, ++i.second)
283 auto allBucketsKeptOriginalOrder =
true;
284 for (
auto i = 0u; i < before.size(); ++i)
286 EXPECT_EQ(before[i].size(),
after[i].size());
287 allBucketsKeptOriginalOrder =
288 allBucketsKeptOriginalOrder && sameEndpoints(before[i],
after[i]);
289 EXPECT_TRUE(sameEndpoints(beforeSorted[i], afterSorted[i]));
291 EXPECT_FALSE(allBucketsKeptOriginalOrder);
T back_inserter(T... args)
Address const & address() const
Returns the address portion of this endpoint.
bool isMember(char const *key) const
Return true if the object has a member named key.
A PropertyStream::Sink which produces a json::Value of type ValueType::Object.
static TestSink & instance()
boost::asio::ip::address Address
boost::asio::ip::address_v6 AddressV6
boost::asio::ip::address_v4 AddressV4
constexpr std::uint32_t kMaxHops
constexpr std::chrono::seconds kLiveCacheSecondsToLive(30)
TEST_F(LivecacheTest, basic_insert)
static auto sum(TCollection const &col)
Integral randInt(Engine &engine, Integral min, Integral max)
Return a uniformly distributed random integer.
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
beast::ManualClock< std::chrono::steady_clock > TestStopwatch
A manual Stopwatch for unit tests.
Describes a connectable peer address along with some metadata.
beast::ip::Endpoint address