mirror of
https://github.com/XRPLF/clio.git
synced 2026-10-09 13:28:08 +00:00
fix: Use a single timestamp in server_info (#3234)
Co-authored-by: snowyukitty <270071858+snowyukitty@users.noreply.github.com>
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
#include "rpc/JS.hpp"
|
||||
#include "rpc/common/Types.hpp"
|
||||
#include "util/Assert.hpp"
|
||||
#include "util/Concepts.hpp"
|
||||
#include "util/build/Build.hpp"
|
||||
|
||||
#include <boost/json/conversion.hpp>
|
||||
@@ -43,8 +44,9 @@ namespace rpc {
|
||||
* @brief Contains common functionality for handling the `server_info` command
|
||||
*
|
||||
* @tparam CountersType The type of the counters
|
||||
* @tparam ClockType Clock used for the output time and the ledger age
|
||||
*/
|
||||
template <typename CountersType>
|
||||
template <typename CountersType, util::SomeSystemClock ClockType = std::chrono::system_clock>
|
||||
class BaseServerInfoHandler
|
||||
: public rpc::spec::HandlerFor<rpc::spec::handlers::server_info::Input> {
|
||||
static constexpr auto kBackendCountersKey = "backend_counters";
|
||||
@@ -95,7 +97,7 @@ public:
|
||||
std::optional<AdminSection> adminSection = std::nullopt;
|
||||
std::string completeLedgers;
|
||||
uint32_t loadFactor = 1u;
|
||||
std::chrono::time_point<std::chrono::system_clock> time = std::chrono::system_clock::now();
|
||||
std::chrono::time_point<std::chrono::system_clock> time = ClockType::now();
|
||||
std::chrono::seconds uptime = {};
|
||||
std::string clioVersion = util::build::getClioVersionString();
|
||||
std::string xrplVersion = xrpl::build_info::getVersionString();
|
||||
@@ -171,8 +173,7 @@ public:
|
||||
return Error{Status{RippledError::RpcInternal}};
|
||||
|
||||
auto output = Output{};
|
||||
auto const sinceEpoch =
|
||||
duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
|
||||
auto const sinceEpoch = duration_cast<seconds>(output.info.time.time_since_epoch()).count();
|
||||
auto const age = static_cast<int32_t>(sinceEpoch) -
|
||||
static_cast<int32_t>(lgrInfo->closeTime.time_since_epoch().count()) -
|
||||
static_cast<int32_t>(kRippleEpochStart);
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <concepts>
|
||||
#include <cstddef>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
@@ -14,6 +16,14 @@ namespace util {
|
||||
template <typename T>
|
||||
concept SomeNumberType = std::is_arithmetic_v<T> && !std::is_same_v<T, bool> && !std::is_const_v<T>;
|
||||
|
||||
/**
|
||||
* @brief Specifies a clock that reports the current time as a system clock time point
|
||||
*/
|
||||
template <typename T>
|
||||
concept SomeSystemClock = requires {
|
||||
{ T::now() } -> std::same_as<std::chrono::system_clock::time_point>;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Checks that the list of given values contains no duplicates
|
||||
*
|
||||
|
||||
56
tests/common/util/TestConstantClock.hpp
Normal file
56
tests/common/util/TestConstantClock.hpp
Normal file
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
/**
|
||||
* @brief A clock that always reports the same instant and counts how often it was read.
|
||||
*
|
||||
* Satisfies util::SomeSystemClock, so it can stand in for std::chrono::system_clock in any
|
||||
* component templated on a clock. The call count makes "the clock was sampled exactly once"
|
||||
* an assertable property, and the fixed instant makes time-derived output deterministic.
|
||||
*
|
||||
* The counter is process-wide: reset it in the fixture constructor of every suite that reads it.
|
||||
*/
|
||||
class TestConstantClock {
|
||||
public:
|
||||
/** @brief The instant now() reports, as a Unix timestamp in seconds. */
|
||||
static constexpr std::uint32_t kNowUnix = 1'700'000'000u;
|
||||
|
||||
/** @brief The instant now() reports. */
|
||||
static constexpr std::chrono::system_clock::time_point kNow{std::chrono::seconds{kNowUnix}};
|
||||
|
||||
/**
|
||||
* @brief Report the fixed instant and count the read
|
||||
*
|
||||
* @return kNow
|
||||
*/
|
||||
static std::chrono::system_clock::time_point
|
||||
now()
|
||||
{
|
||||
++callCounter;
|
||||
return kNow;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief How often now() has been called since the last reset
|
||||
*
|
||||
* @return The call count
|
||||
*/
|
||||
static std::size_t
|
||||
callCount()
|
||||
{
|
||||
return callCounter;
|
||||
}
|
||||
|
||||
/** @brief Set the call count back to zero. */
|
||||
static void
|
||||
resetCounter()
|
||||
{
|
||||
callCounter = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
static inline std::size_t callCounter = 0;
|
||||
};
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "util/MockETLServiceTestFixture.hpp"
|
||||
#include "util/MockLoadBalancer.hpp"
|
||||
#include "util/MockSubscriptionManager.hpp"
|
||||
#include "util/TestConstantClock.hpp"
|
||||
#include "util/TestObject.hpp"
|
||||
|
||||
#include <boost/json/object.hpp>
|
||||
@@ -18,6 +19,8 @@
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <rpcspec/Errors.hpp>
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/protocol/LedgerHeader.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
@@ -27,21 +30,50 @@ using namespace rpc;
|
||||
using namespace data;
|
||||
using namespace testing;
|
||||
|
||||
using TestServerInfoHandler = BaseServerInfoHandler<MockCounters>;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kLedgerHash = "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652";
|
||||
constexpr auto kClientIp = "1.1.1.1";
|
||||
constexpr auto kNowUnix = TestConstantClock::kNowUnix;
|
||||
|
||||
using TestServerInfoHandler = BaseServerInfoHandler<MockCounters, TestConstantClock>;
|
||||
|
||||
} // namespace
|
||||
|
||||
struct RPCServerInfoHandlerTest : HandlerBaseTest, MockLoadBalancerTest, MockCountersTest {
|
||||
RPCServerInfoHandlerTest()
|
||||
{
|
||||
TestConstantClock::resetCounter();
|
||||
backend_->setRange(10, 30);
|
||||
}
|
||||
|
||||
template <typename Callback>
|
||||
void
|
||||
runNormalRequest(xrpl::LedgerHeader const& ledgerHeader, Callback callback)
|
||||
{
|
||||
EXPECT_CALL(*backend_, fetchLedgerBySequence).WillOnce(Return(ledgerHeader));
|
||||
EXPECT_CALL(*backend_, doFetchLedgerObject)
|
||||
.WillOnce(Return(createLegacyFeeSettingBlob(1, 2, 3, 4, 0)));
|
||||
EXPECT_CALL(*mockLoadBalancerPtr_, forwardToRippled(_, Eq(kClientIp), false, _))
|
||||
.WillOnce(Return(std::unexpected{rpc::ClioError::RpcForwardingInvalidResponse}));
|
||||
EXPECT_CALL(*mockCountersPtr_, uptime).WillOnce(Return(std::chrono::seconds{1234}));
|
||||
EXPECT_CALL(*mockETLServicePtr_, isAmendmentBlocked).WillOnce(Return(false));
|
||||
|
||||
auto const handler = AnyHandler{TestServerInfoHandler{
|
||||
backend_,
|
||||
mockSubscriptionManagerPtr_,
|
||||
mockLoadBalancerPtr_,
|
||||
mockETLServicePtr_,
|
||||
*mockCountersPtr_
|
||||
}};
|
||||
|
||||
runSpawn([&](auto yield) {
|
||||
callback(
|
||||
handler.process(boost::json::parse("{}"), Context{yield, {}, false, kClientIp})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
static void
|
||||
validateNormalOutput(rpc::ReturnType const& output)
|
||||
{
|
||||
@@ -57,6 +89,12 @@ struct RPCServerInfoHandlerTest : HandlerBaseTest, MockLoadBalancerTest, MockCou
|
||||
EXPECT_TRUE(info.contains("libxrpl_version"));
|
||||
EXPECT_TRUE(info.contains("validated_ledger"));
|
||||
EXPECT_TRUE(info.contains("time"));
|
||||
EXPECT_EQ(
|
||||
boost::json::value_to<std::string>(info.at("time")),
|
||||
xrpl::to_string(
|
||||
std::chrono::time_point_cast<std::chrono::microseconds>(TestConstantClock::kNow)
|
||||
)
|
||||
);
|
||||
EXPECT_TRUE(info.contains("uptime"));
|
||||
|
||||
auto const& validated = info.at("validated_ledger").as_object();
|
||||
@@ -135,6 +173,7 @@ TEST_F(RPCServerInfoHandlerTest, NoLedgerHeaderErrorsOutWithInternal)
|
||||
auto const err = rpc::makeError(output.result.error());
|
||||
EXPECT_EQ(err.at("error").as_string(), "internal");
|
||||
EXPECT_EQ(err.at("error_message").as_string(), "Internal error.");
|
||||
EXPECT_EQ(TestConstantClock::callCount(), 0u);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -160,42 +199,14 @@ TEST_F(RPCServerInfoHandlerTest, NoFeesErrorsOutWithInternal)
|
||||
auto const err = rpc::makeError(output.result.error());
|
||||
EXPECT_EQ(err.at("error").as_string(), "internal");
|
||||
EXPECT_EQ(err.at("error_message").as_string(), "Internal error.");
|
||||
EXPECT_EQ(TestConstantClock::callCount(), 0u);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(RPCServerInfoHandlerTest, DefaultOutputIsPresent)
|
||||
{
|
||||
MockLoadBalancer* rawBalancerPtr = mockLoadBalancerPtr_.get();
|
||||
MockCounters const* rawCountersPtr = mockCountersPtr_.get();
|
||||
MockETLService const* rawETLServicePtr = mockETLServicePtr_.get();
|
||||
|
||||
auto const ledgerHeader = createLedgerHeader(kLedgerHash, 30, 3); // 3 seconds old
|
||||
EXPECT_CALL(*backend_, fetchLedgerBySequence).WillOnce(Return(ledgerHeader));
|
||||
|
||||
auto const feeBlob = createLegacyFeeSettingBlob(1, 2, 3, 4, 0);
|
||||
EXPECT_CALL(*backend_, doFetchLedgerObject).WillOnce(Return(feeBlob));
|
||||
|
||||
EXPECT_CALL(
|
||||
*rawBalancerPtr, forwardToRippled(testing::_, testing::Eq(kClientIp), false, testing::_)
|
||||
)
|
||||
.WillOnce(Return(std::unexpected{rpc::ClioError::RpcForwardingInvalidResponse}));
|
||||
|
||||
EXPECT_CALL(*rawCountersPtr, uptime).WillOnce(Return(std::chrono::seconds{1234}));
|
||||
|
||||
EXPECT_CALL(*rawETLServicePtr, isAmendmentBlocked).WillOnce(Return(false));
|
||||
|
||||
auto const handler = AnyHandler{TestServerInfoHandler{
|
||||
backend_,
|
||||
mockSubscriptionManagerPtr_,
|
||||
mockLoadBalancerPtr_,
|
||||
mockETLServicePtr_,
|
||||
*mockCountersPtr_
|
||||
}};
|
||||
|
||||
runSpawn([&](auto yield) {
|
||||
auto const req = boost::json::parse("{}");
|
||||
auto const output = handler.process(req, Context{yield, {}, false, kClientIp});
|
||||
|
||||
auto const ledgerHeader = createLedgerHeaderWithUnixTime(kLedgerHash, 30, kNowUnix - 3);
|
||||
runNormalRequest(ledgerHeader, [&](auto const& output) {
|
||||
validateNormalOutput(output);
|
||||
|
||||
// no admin section present by default
|
||||
@@ -206,13 +217,34 @@ TEST_F(RPCServerInfoHandlerTest, DefaultOutputIsPresent)
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(RPCServerInfoHandlerTest, SamplesTheClockOnceForTimeAndAge)
|
||||
{
|
||||
auto const ledgerHeader = createLedgerHeaderWithUnixTime(kLedgerHash, 30, kNowUnix - 3);
|
||||
runNormalRequest(ledgerHeader, [&](auto const& output) {
|
||||
ASSERT_TRUE(output);
|
||||
EXPECT_EQ(TestConstantClock::callCount(), 1u);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(RPCServerInfoHandlerTest, FutureLedgerCloseTimeReportsZeroAge)
|
||||
{
|
||||
auto const ledgerHeader = createLedgerHeaderWithUnixTime(kLedgerHash, 30, kNowUnix + 5);
|
||||
runNormalRequest(ledgerHeader, [&](auto const& output) {
|
||||
ASSERT_TRUE(output);
|
||||
auto const& result = output.result.value().as_object();
|
||||
auto const& info = result.at("info").as_object();
|
||||
auto const& validated = info.at("validated_ledger").as_object();
|
||||
EXPECT_EQ(validated.at("age").as_uint64(), 0u);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(RPCServerInfoHandlerTest, AmendmentBlockedIsPresentIfSet)
|
||||
{
|
||||
MockLoadBalancer* rawBalancerPtr = mockLoadBalancerPtr_.get();
|
||||
MockCounters const* rawCountersPtr = mockCountersPtr_.get();
|
||||
MockETLService const* rawETLServicePtr = mockETLServicePtr_.get();
|
||||
|
||||
auto const ledgerHeader = createLedgerHeader(kLedgerHash, 30, 3); // 3 seconds old
|
||||
auto const ledgerHeader = createLedgerHeaderWithUnixTime(kLedgerHash, 30, kNowUnix - 3);
|
||||
EXPECT_CALL(*backend_, fetchLedgerBySequence).WillOnce(Return(ledgerHeader));
|
||||
|
||||
auto const feeBlob = createLegacyFeeSettingBlob(1, 2, 3, 4, 0);
|
||||
@@ -253,7 +285,7 @@ TEST_F(RPCServerInfoHandlerTest, CorruptionDetectedIsPresentIfSet)
|
||||
MockCounters const* rawCountersPtr = mockCountersPtr_.get();
|
||||
MockETLService const* rawETLServicePtr = mockETLServicePtr_.get();
|
||||
|
||||
auto const ledgerHeader = createLedgerHeader(kLedgerHash, 30, 3); // 3 seconds old
|
||||
auto const ledgerHeader = createLedgerHeaderWithUnixTime(kLedgerHash, 30, kNowUnix - 3);
|
||||
EXPECT_CALL(*backend_, fetchLedgerBySequence).WillOnce(Return(ledgerHeader));
|
||||
|
||||
auto const feeBlob = createLegacyFeeSettingBlob(1, 2, 3, 4, 0);
|
||||
@@ -293,7 +325,7 @@ TEST_F(RPCServerInfoHandlerTest, CacheReportsEnabledFlagCorrectly)
|
||||
MockLoadBalancer* rawBalancerPtr = mockLoadBalancerPtr_.get();
|
||||
MockCounters const* rawCountersPtr = mockCountersPtr_.get();
|
||||
|
||||
auto const ledgerHeader = createLedgerHeader(kLedgerHash, 30, 3); // 3 seconds old
|
||||
auto const ledgerHeader = createLedgerHeaderWithUnixTime(kLedgerHash, 30, kNowUnix - 3);
|
||||
EXPECT_CALL(*backend_, fetchLedgerBySequence).Times(2).WillRepeatedly(Return(ledgerHeader));
|
||||
|
||||
auto const feeBlob = createLegacyFeeSettingBlob(1, 2, 3, 4, 0);
|
||||
@@ -351,7 +383,7 @@ TEST_F(RPCServerInfoHandlerTest, AdminSectionPresentWhenAdminFlagIsSet)
|
||||
MockETLService const* rawETLServicePtr = mockETLServicePtr_.get();
|
||||
|
||||
auto const empty = boost::json::object{};
|
||||
auto const ledgerHeader = createLedgerHeader(kLedgerHash, 30, 3); // 3 seconds old
|
||||
auto const ledgerHeader = createLedgerHeaderWithUnixTime(kLedgerHash, 30, kNowUnix - 3);
|
||||
EXPECT_CALL(*backend_, fetchLedgerBySequence).WillOnce(Return(ledgerHeader));
|
||||
|
||||
auto const feeBlob = createLegacyFeeSettingBlob(1, 2, 3, 4, 0);
|
||||
@@ -394,7 +426,7 @@ TEST_F(RPCServerInfoHandlerTest, BackendCountersPresentWhenRequestWithParam)
|
||||
MockETLService const* rawETLServicePtr = mockETLServicePtr_.get();
|
||||
|
||||
auto const empty = boost::json::object{};
|
||||
auto const ledgerHeader = createLedgerHeader(kLedgerHash, 30, 3); // 3 seconds old
|
||||
auto const ledgerHeader = createLedgerHeaderWithUnixTime(kLedgerHash, 30, kNowUnix - 3);
|
||||
EXPECT_CALL(*backend_, fetchLedgerBySequence).WillOnce(Return(ledgerHeader));
|
||||
|
||||
auto const feeBlob = createLegacyFeeSettingBlob(1, 2, 3, 4, 0);
|
||||
@@ -444,7 +476,7 @@ TEST_F(RPCServerInfoHandlerTest, RippledForwardedValuesPresent)
|
||||
MockETLService const* rawETLServicePtr = mockETLServicePtr_.get();
|
||||
|
||||
auto const empty = boost::json::object{};
|
||||
auto const ledgerHeader = createLedgerHeader(kLedgerHash, 30, 3); // 3 seconds old
|
||||
auto const ledgerHeader = createLedgerHeaderWithUnixTime(kLedgerHash, 30, kNowUnix - 3);
|
||||
EXPECT_CALL(*backend_, fetchLedgerBySequence).WillOnce(Return(ledgerHeader));
|
||||
|
||||
auto const feeBlob = createLegacyFeeSettingBlob(1, 2, 3, 4, 0);
|
||||
@@ -498,7 +530,7 @@ TEST_F(RPCServerInfoHandlerTest, RippledForwardedValuesMissingNoExceptionThrown)
|
||||
MockETLService const* rawETLServicePtr = mockETLServicePtr_.get();
|
||||
|
||||
auto const empty = boost::json::object{};
|
||||
auto const ledgerHeader = createLedgerHeader(kLedgerHash, 30, 3); // 3 seconds old
|
||||
auto const ledgerHeader = createLedgerHeaderWithUnixTime(kLedgerHash, 30, kNowUnix - 3);
|
||||
EXPECT_CALL(*backend_, fetchLedgerBySequence).WillOnce(Return(ledgerHeader));
|
||||
|
||||
auto const feeBlob = createLegacyFeeSettingBlob(1, 2, 3, 4, 0);
|
||||
|
||||
Reference in New Issue
Block a user