From 6ddad54985b89320f6ee03ca1344be317dbcf4bf Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Wed, 29 Jul 2026 23:54:46 +0100 Subject: [PATCH 01/32] chore: Move lexical cast tests to gtest (#7873) --- include/xrpl/beast/core/LexicalCast.h | 16 +- src/test/beast/LexicalCast_test.cpp | 280 ------------------- src/tests/libxrpl/beast/LexicalCast.cpp | 339 ++++++++++++++++++++++++ 3 files changed, 347 insertions(+), 288 deletions(-) delete mode 100644 src/test/beast/LexicalCast_test.cpp create mode 100644 src/tests/libxrpl/beast/LexicalCast.cpp diff --git a/include/xrpl/beast/core/LexicalCast.h b/include/xrpl/beast/core/LexicalCast.h index 7cf21892bd..288c5d6673 100644 --- a/include/xrpl/beast/core/LexicalCast.h +++ b/include/xrpl/beast/core/LexicalCast.h @@ -58,7 +58,7 @@ struct LexicalCast "beast::LexicalCast can only be used with integral types"); template - bool + constexpr bool operator()(Integral& out, std::string_view in) const requires(std::is_integral_v && !std::is_same_v) { @@ -110,7 +110,7 @@ struct LexicalCast> { explicit LexicalCast() = default; - bool + constexpr bool operator()(Out& out, boost::core::basic_string_view in) const { return LexicalCast()(out, in); @@ -123,7 +123,7 @@ struct LexicalCast { explicit LexicalCast() = default; - bool + constexpr bool operator()(Out& out, std::string in) const { return LexicalCast()(out, in); @@ -136,7 +136,7 @@ struct LexicalCast { explicit LexicalCast() = default; - bool + constexpr bool operator()(Out& out, char const* in) const { XRPL_ASSERT(in, "beast::detail::LexicalCast(char const*) : non-null input"); @@ -151,7 +151,7 @@ struct LexicalCast { explicit LexicalCast() = default; - bool + constexpr bool operator()(Out& out, char* in) const { XRPL_ASSERT(in, "beast::detail::LexicalCast(char*) : non-null input"); @@ -177,7 +177,7 @@ struct BadLexicalCast : public std::bad_cast * @return `false` if there was a parsing or range error */ template -bool +constexpr bool lexicalCastChecked(Out& out, In in) { return detail::LexicalCast()(out, in); @@ -191,7 +191,7 @@ lexicalCastChecked(Out& out, In in) * @return The new type. */ template -Out +constexpr Out lexicalCastThrow(In in) { if (Out out; lexicalCastChecked(out, in)) @@ -207,7 +207,7 @@ lexicalCastThrow(In in) * @return The new type. */ template -Out +constexpr Out lexicalCast(In in, Out defaultValue = Out()) { if (Out out; lexicalCastChecked(out, in)) diff --git a/src/test/beast/LexicalCast_test.cpp b/src/test/beast/LexicalCast_test.cpp deleted file mode 100644 index b1d37daab8..0000000000 --- a/src/test/beast/LexicalCast_test.cpp +++ /dev/null @@ -1,280 +0,0 @@ -#include -#include -#include - -#include -#include -#include -#include - -namespace beast { - -class LexicalCast_test : public unit_test::Suite -{ -public: - template - static IntType - nextRandomInt(xor_shift_engine& r) - { - return static_cast(r()); - } - - template - void - testInteger(IntType in) - { - std::string s; - auto out = static_cast(~in); // Ensure out != in - - expect(lexicalCastChecked(s, in)); - expect(lexicalCastChecked(out, s)); - expect(out == in); - } - - template - void - testIntegers(xor_shift_engine& r) - { - { - std::stringstream ss; - ss << "random " << typeid(IntType).name(); - testcase(ss.str()); - - for (int i = 0; i < 1000; ++i) - { - auto const value = nextRandomInt(r); - testInteger(value); - } - } - - { - std::stringstream ss; - ss << "numeric_limits <" << typeid(IntType).name() << ">"; - testcase(ss.str()); - - testInteger(std::numeric_limits::min()); - testInteger(std::numeric_limits::max()); - } - } - - void - testPathologies() - { - testcase("pathologies"); - try - { - lexicalCastThrow("\xef\xbc\x91\xef\xbc\x90"); // utf-8 encoded - } - catch (BadLexicalCast const&) - { - pass(); - } - } - - template - void - tryBadConvert(std::string const& s) - { - T out; - expect(!lexicalCastChecked(out, s), s); - } - - void - testConversionOverflows() - { - testcase("conversion overflows"); - - tryBadConvert("99999999999999999999"); - tryBadConvert("4294967300"); - tryBadConvert("75821"); - } - - void - testConversionUnderflows() - { - testcase("conversion underflows"); - - tryBadConvert("-1"); - - tryBadConvert("-99999999999999999999"); - tryBadConvert("-4294967300"); - tryBadConvert("-75821"); - } - - template - bool - tryEdgeCase(std::string const& s) - { - T ret; - - bool const result = lexicalCastChecked(ret, s); - - if (!result) - return false; - - return s == std::to_string(ret); - } - - void - testEdgeCases() - { - testcase("conversion edge cases"); - - expect(tryEdgeCase("18446744073709551614")); - expect(tryEdgeCase("18446744073709551615")); - expect(!tryEdgeCase("18446744073709551616")); - - expect(tryEdgeCase("9223372036854775806")); - expect(tryEdgeCase("9223372036854775807")); - expect(!tryEdgeCase("9223372036854775808")); - - expect(tryEdgeCase("-9223372036854775807")); - expect(tryEdgeCase("-9223372036854775808")); - expect(!tryEdgeCase("-9223372036854775809")); - - expect(tryEdgeCase("4294967294")); - expect(tryEdgeCase("4294967295")); - expect(!tryEdgeCase("4294967296")); - - expect(tryEdgeCase("2147483646")); - expect(tryEdgeCase("2147483647")); - expect(!tryEdgeCase("2147483648")); - - expect(tryEdgeCase("-2147483647")); - expect(tryEdgeCase("-2147483648")); - expect(!tryEdgeCase("-2147483649")); - - expect(tryEdgeCase("65534")); - expect(tryEdgeCase("65535")); - expect(!tryEdgeCase("65536")); - - expect(tryEdgeCase("32766")); - expect(tryEdgeCase("32767")); - expect(!tryEdgeCase("32768")); - - expect(tryEdgeCase("-32767")); - expect(tryEdgeCase("-32768")); - expect(!tryEdgeCase("-32769")); - } - - template - void - testThrowConvert(std::string const& s, bool success) - { - bool result = !success; - T out; - - try - { - out = lexicalCastThrow(s); - result = true; - } - catch (BadLexicalCast const&) - { - result = false; - } - - expect(result == success, s); - } - - void - testThrowingConversions() - { - testcase("throwing conversion"); - - testThrowConvert("99999999999999999999", false); - testThrowConvert("9223372036854775806", true); - - testThrowConvert("4294967290", true); - testThrowConvert("42949672900", false); - testThrowConvert("429496729000", false); - testThrowConvert("4294967290000", false); - - testThrowConvert("5294967295", false); - testThrowConvert("-2147483644", true); - - testThrowConvert("66666", false); - testThrowConvert("-5711", true); - } - - void - testZero() - { - testcase("zero conversion"); - - { - std::int32_t out = 0; - - expect(lexicalCastChecked(out, "-0"), "0"); - expect(lexicalCastChecked(out, "0"), "0"); - expect(lexicalCastChecked(out, "+0"), "0"); - } - - { - std::uint32_t out = 0; - - expect(!lexicalCastChecked(out, "-0"), "0"); - expect(lexicalCastChecked(out, "0"), "0"); - expect(lexicalCastChecked(out, "+0"), "0"); - } - } - - void - testEntireRange() - { - testcase("entire range"); - - std::int32_t i = std::numeric_limits::min(); - std::string const empty; - - while (i <= std::numeric_limits::max()) - { - auto const j = static_cast(i); - - auto actual = std::to_string(j); - - auto result = lexicalCast(j, empty); - - expect(result == actual, actual + " (string to integer)"); - - if (result == actual) - { - auto number = lexicalCast(result); - - if (number != j) - expect(false, actual + " (integer to string)"); - } - - i++; - } - } - - void - run() override - { - std::int64_t const seedValue = 50; - - xor_shift_engine r(seedValue); - - testIntegers(r); - testIntegers(r); - testIntegers(r); - testIntegers(r); - testIntegers(r); - testIntegers(r); - testIntegers(r); - testIntegers(r); - - testPathologies(); - testConversionOverflows(); - testConversionUnderflows(); - testThrowingConversions(); - testZero(); - testEdgeCases(); - testEntireRange(); - } -}; - -BEAST_DEFINE_TESTSUITE(LexicalCast, beast, beast); - -} // namespace beast diff --git a/src/tests/libxrpl/beast/LexicalCast.cpp b/src/tests/libxrpl/beast/LexicalCast.cpp new file mode 100644 index 0000000000..d18af4e1cd --- /dev/null +++ b/src/tests/libxrpl/beast/LexicalCast.cpp @@ -0,0 +1,339 @@ +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace { + +template +[[nodiscard]] constexpr bool +parses(std::string_view text) +{ + T out{}; + return lexicalCastChecked(out, text); +} + +template +[[nodiscard]] constexpr T +parsed(std::string_view text) +{ + T out{}; + return lexicalCastChecked(out, text) ? out : T{}; +} + +template +constexpr T kMax = std::numeric_limits::max(); + +template +constexpr T kMin = std::numeric_limits::min(); + +template +constexpr T kUnderMax = kMax - 1; + +template +constexpr T kOverMin = kMin + 1; + +// Comfortably inside the range, not boundary values. +constexpr auto kNearMax32 = kMax - 5; +constexpr auto kNearMin32 = kMin + 4; +constexpr auto kUnderInt64Max = uint64_t{kMax} - 1; +constexpr auto kInRangeInt16 = int16_t{-5711}; + +// No wider integer type can hold these, so ToString cannot produce them. +constexpr auto kAboveUint64Max = "18446744073709551616"; +constexpr auto kBelowInt64Min = "-9223372036854775809"; + +// Out of range for every integer type we test. +constexpr auto kTwentyNines = "99999999999999999999"; +constexpr auto kNegativeTwentyNines = "-99999999999999999999"; + +// Arbitrary values chosen to sit well outside a type's range, not just over it. +constexpr auto kAboveUint16Max = "75821"; +constexpr auto kBelowInt16Min = "-75821"; +constexpr auto kAboveInt32Max = "5294967295"; +constexpr auto kAboveInt16Max = "66666"; + +constexpr auto kPositiveInt32 = int32_t{42}; +constexpr auto kNegativeInt32 = int32_t{-42}; + +constexpr auto kPositiveInt32Text = "+42"; +constexpr auto kNegativeInt32Text = "-42"; + +constexpr auto kNegativeOne = "-1"; +constexpr auto kNegativeZero = "-0"; +constexpr auto kBareZero = "0"; +constexpr auto kPositiveZero = "+0"; + +// Full-width digits one and zero, not ASCII ones. +constexpr std::string_view kFullWidthDigits = "\xef\xbc\x91\xef\xbc\x90"; + +// The decimal text of a value, usable in a constant expression. +template +struct ToString +{ + std::array buffer{}; + std::size_t length{}; + + constexpr explicit ToString(T value) + { + auto const result = std::to_chars(buffer.data(), buffer.data() + buffer.size(), value); + length = static_cast(result.ptr - buffer.data()); + } + + constexpr + operator std::string_view() const + { + return {buffer.data(), length}; + } +}; + +template +constexpr auto kMaxText = ToString{kMax}; + +template +constexpr auto kUnderMaxText = ToString{kUnderMax}; + +template +constexpr auto kOverMaxText = ToString{Wider{kMax} + 1}; + +template +constexpr auto kMinText = ToString{kMin}; + +template +constexpr auto kOverMinText = ToString{kOverMin}; + +template +constexpr auto kUnderMinText = ToString{Wider{kMin} - 1}; + +constexpr auto kOverUint32MaxText = ToString{uint64_t{kMax} + 5}; +constexpr auto kNegatedOverUint32MaxText = ToString{-(int64_t{kMax} + 5)}; + +// lexicalCastThrow deduces its input type, so the text has to be an explicit +// string_view rather than a ToString. +template +[[nodiscard]] constexpr T +castThrow(Value value) +{ + return lexicalCastThrow(std::string_view{ToString{value}}); +} + +template +[[nodiscard]] bool +roundTrips(std::string_view text) +{ + T out{}; + return lexicalCastChecked(out, text) && std::to_string(out) == text; +} + +template +void +expectRoundTrip(T value) +{ + SCOPED_TRACE(::testing::Message() << "value: " << value); + + auto const text = lexicalCast(value); + EXPECT_EQ(text, std::to_string(value)); + + auto decoded = static_cast(~value); // ensure decoded != value + EXPECT_TRUE(lexicalCastChecked(decoded, text)); + EXPECT_EQ(decoded, value); +} + +} // namespace + +// int/unsigned/short/unsigned short are covered by the list below — they are +// these exact types everywhere we build. +static_assert(std::is_same_v); +static_assert(std::is_same_v); +static_assert(std::is_same_v); +static_assert(std::is_same_v); + +using IntegerTypes = ::testing::Types< // + int16_t, + uint16_t, + int32_t, + uint32_t, + int64_t, + uint64_t>; + +struct IntegerTypeNames +{ + template + static std::string + // NOLINTNEXTLINE(readability-identifier-naming) - required by gtest + GetName(int) + { + return (std::is_signed_v ? "int" : "uint") + std::to_string(sizeof(T) * 8) + "_t"; + } +}; + +template +class LexicalCastIntegers : public ::testing::Test +{ +}; + +TYPED_TEST_SUITE(LexicalCastIntegers, IntegerTypes, IntegerTypeNames); + +TYPED_TEST(LexicalCastIntegers, round_trips_random_values) +{ + static constexpr auto kSampleCount = 1000uz; + + xor_shift_engine r{50}; // seeded per test so a failure reproduces on its own + + for (auto i = 0uz; i < kSampleCount; ++i) + expectRoundTrip(static_cast(r())); +} + +TYPED_TEST(LexicalCastIntegers, round_trips_numeric_limits) +{ + expectRoundTrip(std::numeric_limits::min()); + expectRoundTrip(std::numeric_limits::max()); +} + +TEST(LexicalCast, round_trips_every_int16_value) +{ + for (int32_t i = kMin; i <= kMax; ++i) + { + auto const value = static_cast(i); + + // ASSERT, or a broken cast reports all 65536 iterations. + auto const text = lexicalCast(value); + ASSERT_EQ(text, std::to_string(value)); + ASSERT_EQ(lexicalCast(text), value); + } +} + +TEST(LexicalCast, rejects_overflow) +{ + static_assert(not parses(kOverUint32MaxText)); + static_assert(not parses(kTwentyNines)); + static_assert(not parses(kAboveUint16Max)); +} + +TEST(LexicalCast, rejects_underflow) +{ + static_assert(not parses(kNegativeOne)); + static_assert(not parses(kNegatedOverUint32MaxText)); + static_assert(not parses(kNegativeTwentyNines)); + static_assert(not parses(kBelowInt16Min)); +} + +TEST(LexicalCast, accepts_up_to_the_maximum) +{ + static_assert(parsed(kUnderMaxText) == kUnderMax); + static_assert(parsed(kMaxText) == kMax); + static_assert(not parses(kOverMaxText)); + + static_assert(parsed(kUnderMaxText) == kUnderMax); + static_assert(parsed(kMaxText) == kMax); + static_assert(not parses(kOverMaxText)); + + static_assert(parsed(kUnderMaxText) == kUnderMax); + static_assert(parsed(kMaxText) == kMax); + static_assert(not parses(kOverMaxText)); + + static_assert(parsed(kUnderMaxText) == kUnderMax); + static_assert(parsed(kMaxText) == kMax); + static_assert(not parses(kOverMaxText)); + + static_assert(parsed(kUnderMaxText) == kUnderMax); + static_assert(parsed(kMaxText) == kMax); + static_assert(not parses(kOverMaxText)); + + static_assert(parsed(kUnderMaxText) == kUnderMax); + static_assert(parsed(kMaxText) == kMax); + static_assert(not parses(kAboveUint64Max)); +} + +TEST(LexicalCast, accepts_down_to_the_minimum) +{ + static_assert(parsed(kOverMinText) == kOverMin); + static_assert(parsed(kMinText) == kMin); + static_assert(not parses(kUnderMinText)); + + static_assert(parsed(kOverMinText) == kOverMin); + static_assert(parsed(kMinText) == kMin); + static_assert(not parses(kUnderMinText)); + + static_assert(parsed(kOverMinText) == kOverMin); + static_assert(parsed(kMinText) == kMin); + static_assert(not parses(kBelowInt64Min)); +} + +TEST(LexicalCast, limits_round_trip_through_to_string) +{ + EXPECT_TRUE(roundTrips(kMaxText)); + EXPECT_TRUE(roundTrips(kMaxText)); + EXPECT_TRUE(roundTrips(kMinText)); + EXPECT_TRUE(roundTrips(kMaxText)); + EXPECT_TRUE(roundTrips(kMinText)); + EXPECT_TRUE(roundTrips(kMaxText)); + EXPECT_TRUE(roundTrips(kMinText)); +} + +TEST(LexicalCast, accepts_signed_zero_in_every_form) +{ + static_assert(parsed(kNegativeZero) == 0); + static_assert(parsed(kBareZero) == 0); + static_assert(parsed(kPositiveZero) == 0); +} + +TEST(LexicalCast, rejects_negative_zero_when_unsigned) +{ + static_assert(not parses(kNegativeZero)); + static_assert(parsed(kBareZero) == 0); + static_assert(parsed(kPositiveZero) == 0); +} + +TEST(LexicalCast, accepts_char_pointer_and_std_string_input) +{ + int32_t fromLiteral = 0; + EXPECT_TRUE(lexicalCastChecked(fromLiteral, kPositiveInt32Text)); + EXPECT_EQ(fromLiteral, kPositiveInt32); + + int32_t fromString = 0; + EXPECT_TRUE(lexicalCastChecked(fromString, std::string{kNegativeInt32Text})); + EXPECT_EQ(fromString, kNegativeInt32); +} + +TEST(LexicalCast, throwing_cast_returns_in_range_values) +{ + static_assert(castThrow(kUnderInt64Max) == kUnderInt64Max); + static_assert(castThrow(kNearMax32) == kNearMax32); + static_assert(castThrow(kNearMin32) == kNearMin32); + static_assert(castThrow(kInRangeInt16) == kInRangeInt16); +} + +TEST(LexicalCast, throwing_cast_throws_on_out_of_range) +{ + EXPECT_THROW(lexicalCastThrow(kTwentyNines), BadLexicalCast); + + // kNearMax32 with digits appended, so each is further past uint32_t's range. + for (auto const scale : {10, 100, 1000}) + { + auto const tooBig = ToString{uint64_t{kNearMax32} * scale}; + EXPECT_THROW(lexicalCastThrow(std::string_view{tooBig}), BadLexicalCast); + } + + EXPECT_THROW(lexicalCastThrow(kAboveInt32Max), BadLexicalCast); + EXPECT_THROW(lexicalCastThrow(kAboveInt16Max), BadLexicalCast); +} + +// Full-width digits, not ASCII ones. +TEST(LexicalCast, throwing_cast_throws_on_utf8_digits) +{ + EXPECT_THROW(lexicalCastThrow(kFullWidthDigits), BadLexicalCast); +} + +} // namespace beast From 8a5eded4f10ef7df02b9d5135fa5a7837f9070f8 Mon Sep 17 00:00:00 2001 From: Vito Tumas <5780819+Tapanito@users.noreply.github.com> Date: Thu, 30 Jul 2026 13:55:39 +0200 Subject: [PATCH 02/32] feat: Implement LoanBroker cash-basis accounting (#7817) --- include/xrpl/ledger/helpers/LendingHelpers.h | 71 ++ include/xrpl/ledger/helpers/VaultHelpers.h | 16 + include/xrpl/protocol/Protocol.h | 11 + .../xrpl/protocol/detail/ledger_entries.macro | 1 + include/xrpl/protocol/detail/sfields.macro | 1 + .../protocol_autogen/ledger_entries/Vault.h | 35 + src/libxrpl/ledger/helpers/LendingHelpers.cpp | 122 ++ src/libxrpl/ledger/helpers/VaultHelpers.cpp | 20 + .../tx/transactors/lending/LoanManage.cpp | 23 +- .../tx/transactors/lending/LoanPay.cpp | 43 +- .../tx/transactors/lending/LoanSet.cpp | 15 +- .../tx/transactors/vault/VaultCreate.cpp | 3 + src/test/app/LendingHelpers_test.cpp | 333 ++++++ src/test/app/Loan_test.cpp | 1011 ++++++++++++++++- src/test/app/Vault_test.cpp | 78 ++ .../ledger_entries/VaultTests.cpp | 27 + 16 files changed, 1749 insertions(+), 61 deletions(-) diff --git a/include/xrpl/ledger/helpers/LendingHelpers.h b/include/xrpl/ledger/helpers/LendingHelpers.h index 8e0d11cccb..fef18e3e09 100644 --- a/include/xrpl/ledger/helpers/LendingHelpers.h +++ b/include/xrpl/ledger/helpers/LendingHelpers.h @@ -286,6 +286,77 @@ computeFullPaymentInterest( std::uint32_t startDate, TenthBips32 closeInterestRate); +// Deltas applied to Vault.AssetsTotal and LoanBroker.DebtTotal at a single +// accounting touch point (origination, payment, impair/unimpair/default). +struct AccountingDeltas +{ + Number assetsTotalDelta; + Number debtTotalDelta; +}; + +// Whole-life (pre-LendingProtocolV1_1) recognition model: interest is +// recognized into AssetsTotal/DebtTotal up front, at origination. +namespace Accrual { + +// LoanSet origination: what's added to Vault.AssetsTotal and LoanBroker.DebtTotal +AccountingDeltas +loanOriginationDeltas(Number const& principalRequested, Number const& interestDue); + +// LoanSet origination: would recognizing this loan's interest push +// Vault.AssetsTotal past Vault.AssetsMaximum? +bool +loanOriginationExceedsVaultMaximum( + Number const& vaultMaximum, + Number const& vaultTotal, + Number const& interestDue); + +// LoanManage impair/unimpair/default: the vault's exposure to this loan +Number +loanVaultExposure(SLE::const_ref loanSle); + +// LoanPay: what's added to Vault.AssetsTotal and subtracted from LoanBroker.DebtTotal for a payment +AccountingDeltas +loanPaymentDeltas(LoanPaymentParts const& parts); + +} // namespace Accrual + +// Cash-basis (LendingProtocolV1_1) recognition model: AssetsTotal/DebtTotal +// are principal-only, interest is recognized only as it's actually paid. +namespace CashBasis { + +AccountingDeltas +loanOriginationDeltas(Number const& principalRequested); + +Number +loanVaultExposure(SLE::const_ref loanSle); + +AccountingDeltas +loanPaymentDeltas(LoanPaymentParts const& parts); + +} // namespace CashBasis + +// Public dispatchers: pick CashBasis:: if featureLendingProtocolV1_1 is +// enabled AND the Vault's LEVersion (VaultHelpers::getVaultVersion) is +// VaultVersion::CashBasis, else Accrual::. These are the only entry points +// transactors call. +AccountingDeltas +loanOriginationDeltas( + SLE::const_ref vaultSle, + Number const& principalRequested, + Number const& interestDue); + +bool +loanOriginationExceedsVaultMaximum( + SLE::const_ref vaultSle, + Number const& vaultTotal, + Number const& interestDue); + +Number +loanVaultExposure(SLE::const_ref vaultSle, SLE::const_ref loanSle); + +AccountingDeltas +loanPaymentDeltas(SLE::const_ref vaultSle, LoanPaymentParts const& parts); + namespace detail { // These classes and functions should only be accessed by LendingHelper // functions and unit tests diff --git a/include/xrpl/ledger/helpers/VaultHelpers.h b/include/xrpl/ledger/helpers/VaultHelpers.h index 1bd1663314..5681cc57e8 100644 --- a/include/xrpl/ledger/helpers/VaultHelpers.h +++ b/include/xrpl/ledger/helpers/VaultHelpers.h @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -107,4 +108,19 @@ sharesToAssetsWithdraw( [[nodiscard]] bool isSoleShareholder(ReadView const& view, AccountID const& account, SLE::const_ref issuance); +/** + * Resolves a Vault's LEVersion, the single point every accounting touch + * point should call to determine which recognition model (accrual vs. + * cash-basis) a Vault uses. Vaults created before featureLendingProtocolV1_1 + * activated never have sfLEVersion set, which resolves here to + * VaultVersion::Legacy. + * + * @param vault The vault SLE. + * + * @return The Vault's LEVersion, or VaultVersion::Legacy if the field is + * absent. + */ +[[nodiscard]] VaultVersion +getVaultVersion(SLE::const_ref vault); + } // namespace xrpl diff --git a/include/xrpl/protocol/Protocol.h b/include/xrpl/protocol/Protocol.h index e83e1c97b6..9938a9b768 100644 --- a/include/xrpl/protocol/Protocol.h +++ b/include/xrpl/protocol/Protocol.h @@ -316,6 +316,17 @@ constexpr std::uint8_t kVaultDefaultIouScale = 6; */ constexpr std::uint8_t kVaultMaximumIouScale = 18; +/** + * Vault ledger-entry schema versions. Assigned to newly created + * Vaults once featureLendingProtocolV1_1 is enabled. Vaults created before + * activation are left without LEVersion (implicit legacy version 0, + * accrual-basis accounting). + */ +enum class VaultVersion : uint8_t { + Legacy = 0, + CashBasis, +}; + /** * Maximum recursion depth for vault shares being put as an asset inside * another vault; counted from 0 diff --git a/include/xrpl/protocol/detail/ledger_entries.macro b/include/xrpl/protocol/detail/ledger_entries.macro index 90810e06d2..b6408581a9 100644 --- a/include/xrpl/protocol/detail/ledger_entries.macro +++ b/include/xrpl/protocol/detail/ledger_entries.macro @@ -505,6 +505,7 @@ LEDGER_ENTRY(ltVAULT, 0x0084, Vault, vault, ({ {sfShareMPTID, SoeRequired}, {sfWithdrawalPolicy, SoeRequired}, {sfScale, SoeDefault}, + {sfLEVersion, SoeDefault}, // no SharesTotal ever (use MPTIssuance.sfOutstandingAmount) // no PermissionedDomainID ever (use MPTIssuance.sfDomainID) })) diff --git a/include/xrpl/protocol/detail/sfields.macro b/include/xrpl/protocol/detail/sfields.macro index 4ef76c8b75..16defe3ba3 100644 --- a/include/xrpl/protocol/detail/sfields.macro +++ b/include/xrpl/protocol/detail/sfields.macro @@ -18,6 +18,7 @@ TYPED_SFIELD(sfMethod, UINT8, 2) TYPED_SFIELD(sfTransactionResult, UINT8, 3) TYPED_SFIELD(sfScale, UINT8, 4) TYPED_SFIELD(sfAssetScale, UINT8, 5) +TYPED_SFIELD(sfLEVersion, UINT8, 6) // 8-bit integers (uncommon) TYPED_SFIELD(sfTickSize, UINT8, 16) diff --git a/include/xrpl/protocol_autogen/ledger_entries/Vault.h b/include/xrpl/protocol_autogen/ledger_entries/Vault.h index 2bf92b4f5d..a6ab54cb0a 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/Vault.h +++ b/include/xrpl/protocol_autogen/ledger_entries/Vault.h @@ -287,6 +287,30 @@ public: { return this->sle_->isFieldPresent(sfScale); } + + /** + * @brief Get sfLEVersion (SoeDefault) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getLEVersion() const + { + if (hasLEVersion()) + return this->sle_->at(sfLEVersion); + return std::nullopt; + } + + /** + * @brief Check if sfLEVersion is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasLEVersion() const + { + return this->sle_->isFieldPresent(sfLEVersion); + } }; /** @@ -508,6 +532,17 @@ public: return *this; } + /** + * @brief Set sfLEVersion (SoeDefault) + * @return Reference to this builder for method chaining. + */ + VaultBuilder& + setLEVersion(std::decay_t const& value) + { + object_[sfLEVersion] = value; + return *this; + } + /** * @brief Build and return the completed Vault wrapper. * @param index The ledger entry index. diff --git a/src/libxrpl/ledger/helpers/LendingHelpers.cpp b/src/libxrpl/ledger/helpers/LendingHelpers.cpp index e6c3d632c1..dac2c67181 100644 --- a/src/libxrpl/ledger/helpers/LendingHelpers.cpp +++ b/src/libxrpl/ledger/helpers/LendingHelpers.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -130,6 +131,127 @@ isRounded(Asset const& asset, Number const& value, std::int32_t scale) roundToAsset(asset, value, scale, Number::RoundingMode::Upward); } +namespace Accrual { + +AccountingDeltas +loanOriginationDeltas(Number const& principalRequested, Number const& interestDue) +{ + return {.assetsTotalDelta = interestDue, .debtTotalDelta = principalRequested + interestDue}; +} + +bool +loanOriginationExceedsVaultMaximum( + Number const& vaultMaximum, + Number const& vaultTotal, + Number const& interestDue) +{ + return vaultMaximum != 0 && interestDue > vaultMaximum - vaultTotal; +} + +/* +XLS-66 section 3.2.3.2, defines the default amount as + +DefaultAmount = (Loan.PrincipalOutstanding + Loan.InterestOutstanding) + +Which is equivalent to (Loan.TotalValueOutstanding - Loan.ManagementFeeOutstanding) +*/ +Number +loanVaultExposure(SLE::const_ref loanSle) +{ + return loanSle->at(sfTotalValueOutstanding) - loanSle->at(sfManagementFeeOutstanding); +} + +AccountingDeltas +loanPaymentDeltas(LoanPaymentParts const& parts) +{ + return { + .assetsTotalDelta = parts.valueChange, + .debtTotalDelta = (parts.principalPaid + parts.interestPaid) - parts.valueChange}; +} + +} // namespace Accrual + +namespace CashBasis { + +AccountingDeltas +loanOriginationDeltas(Number const& principalRequested) +{ + return {.assetsTotalDelta = kNumZero, .debtTotalDelta = principalRequested}; +} + +/* + * Under CashBasis accounting, Loan default amount is: + * + * DefaultAmount = Loan.PrincipalOutstanding + */ +Number +loanVaultExposure(SLE::const_ref loanSle) +{ + return loanSle->at(sfPrincipalOutstanding); +} + +AccountingDeltas +loanPaymentDeltas(LoanPaymentParts const& parts) +{ + return {.assetsTotalDelta = parts.interestPaid, .debtTotalDelta = parts.principalPaid}; +} + +} // namespace CashBasis + +namespace { + +// Cash-basis accounting applies only when featureLendingProtocolV1_1 is +// enabled AND the specific Vault was created under it (LEVersion == +// VaultVersion::CashBasis). Vaults created before activation keep accrual-basis +// accounting forever, even after the amendment later turns on. +bool +cashBasisEnabled(SLE::const_ref vaultSle) +{ + return getVaultVersion(vaultSle) == VaultVersion::CashBasis; +} + +} // namespace + +AccountingDeltas +loanOriginationDeltas( + SLE::const_ref vaultSle, + Number const& principalRequested, + Number const& interestDue) +{ + return cashBasisEnabled(vaultSle) + ? CashBasis::loanOriginationDeltas(principalRequested) + : Accrual::loanOriginationDeltas(principalRequested, interestDue); +} + +bool +loanOriginationExceedsVaultMaximum( + SLE::const_ref vaultSle, + Number const& vaultTotal, + Number const& interestDue) +{ + // Cash-basis origination doesn't recognize interest into AssetsTotal, so + // interest due can never push the vault past AssetsMaximum at origination. + if (cashBasisEnabled(vaultSle)) + return false; + + auto const vaultMaximum = vaultSle->at(sfAssetsMaximum); + return Accrual::loanOriginationExceedsVaultMaximum(vaultMaximum, vaultTotal, interestDue); +} + +Number +loanVaultExposure(SLE::const_ref vaultSle, SLE::const_ref loanSle) +{ + return cashBasisEnabled(vaultSle) ? CashBasis::loanVaultExposure(loanSle) + : Accrual::loanVaultExposure(loanSle); +} + +AccountingDeltas +loanPaymentDeltas(SLE::const_ref vaultSle, LoanPaymentParts const& parts) +{ + return cashBasisEnabled(vaultSle) ? CashBasis::loanPaymentDeltas(parts) + : Accrual::loanPaymentDeltas(parts); +} + namespace detail { void diff --git a/src/libxrpl/ledger/helpers/VaultHelpers.cpp b/src/libxrpl/ledger/helpers/VaultHelpers.cpp index b5b076d1cb..78f64d2077 100644 --- a/src/libxrpl/ledger/helpers/VaultHelpers.cpp +++ b/src/libxrpl/ledger/helpers/VaultHelpers.cpp @@ -6,6 +6,7 @@ #include #include #include // IWYU pragma: keep +#include #include #include #include @@ -13,6 +14,7 @@ #include #include +#include namespace xrpl { @@ -137,4 +139,22 @@ isSoleShareholder(ReadView const& view, AccountID const& account, SLE::const_ref return sleToken->getFieldU64(sfMPTAmount) == outstanding; } +[[nodiscard]] VaultVersion +getVaultVersion(SLE::const_ref vault) +{ + XRPL_ASSERT(vault && vault->getType() == ltVAULT, "xrpl::getVaultVersion : valid Vault sle"); + if (!vault->isFieldPresent(sfLEVersion)) + return VaultVersion::Legacy; + + auto const version = vault->at(sfLEVersion); + if (version > std::to_underlying(VaultVersion::CashBasis)) + { + // LCOV_EXCL_START + UNREACHABLE("xrpl::getVaultVersion : invalid vault version"); + return VaultVersion::Legacy; + // LCOV_EXCL_STOP + } + return static_cast(version); +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/lending/LoanManage.cpp b/src/libxrpl/tx/transactors/lending/LoanManage.cpp index a0aa948876..a312dba3b3 100644 --- a/src/libxrpl/tx/transactors/lending/LoanManage.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanManage.cpp @@ -127,23 +127,6 @@ LoanManage::preclaim(PreclaimContext const& ctx) return tesSUCCESS; } -static Number -owedToVault(SLE::ref loanSle) -{ - // Spec section 3.2.3.2, defines the default amount as - // - // DefaultAmount = (Loan.PrincipalOutstanding + Loan.InterestOutstanding) - // - // Loan.InterestOutstanding is not stored directly on ledger. - // It is computed as - // - // Loan.TotalValueOutstanding - Loan.PrincipalOutstanding - - // Loan.ManagementFeeOutstanding - // - // Add that to the original formula, and you get this: - return loanSle->at(sfTotalValueOutstanding) - loanSle->at(sfManagementFeeOutstanding); -} - TER LoanManage::defaultLoan( ApplyView& view, @@ -158,7 +141,7 @@ LoanManage::defaultLoan( std::int32_t const loanScale = loanSle->at(sfLoanScale); auto brokerDebtTotalProxy = brokerSle->at(sfDebtTotal); - Number const totalDefaultAmount = owedToVault(loanSle); + Number const totalDefaultAmount = loanVaultExposure(vaultSle, loanSle); // Apply the First-Loss Capital to the Default Amount TenthBips32 const coverRateMinimum{brokerSle->at(sfCoverRateMinimum)}; @@ -304,7 +287,7 @@ LoanManage::impairLoan( Asset const& vaultAsset, beast::Journal j) { - Number const lossUnrealized = owedToVault(loanSle); + Number const lossUnrealized = loanVaultExposure(vaultSle, loanSle); // The vault may be at a different scale than the loan. Reduce rounding // errors during the accounting by rounding some of the values to that @@ -353,7 +336,7 @@ LoanManage::unimpairLoan( // Update the Vault object(clear "paper loss") auto vaultLossUnrealizedProxy = vaultSle->at(sfLossUnrealized); - Number const lossReversed = owedToVault(loanSle); + Number const lossReversed = loanVaultExposure(vaultSle, loanSle); if (vaultLossUnrealizedProxy < lossReversed) { // LCOV_EXCL_START diff --git a/src/libxrpl/tx/transactors/lending/LoanPay.cpp b/src/libxrpl/tx/transactors/lending/LoanPay.cpp index 54ee85b186..0053ed496e 100644 --- a/src/libxrpl/tx/transactors/lending/LoanPay.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanPay.cpp @@ -420,10 +420,13 @@ LoanPay::doApply() // LCOV_EXCL_STOP } + auto const [assetsTotalDelta, debtTotalDelta] = loanPaymentDeltas(vaultSle, *paymentParts); + JLOG(j_.debug()) << "Loan Pay: principal paid: " << paymentParts->principalPaid << ", interest paid: " << paymentParts->interestPaid << ", fee paid: " << paymentParts->feePaid - << ", value change: " << paymentParts->valueChange; + << ", assets total delta: " << assetsTotalDelta + << ", debt total delta: " << debtTotalDelta; //------------------------------------------------------ // LoanBroker object state changes @@ -439,13 +442,6 @@ LoanPay::doApply() !asset.integral() || totalPaidToVaultRaw == totalPaidToVaultRounded, "xrpl::LoanPay::doApply", "rounding does nothing for integral asset"); - // Account for value changes when reducing the broker's debt: - // - Positive value change (from full/late/overpayments): Subtract from the - // amount credited toward debt to avoid over-reducing the debt. - // - Negative value change (from full/overpayments): Add to the amount - // credited toward debt,effectively increasing the debt reduction. - auto const totalPaidToVaultForDebt = totalPaidToVaultRaw - paymentParts->valueChange; - auto const totalPaidToBroker = paymentParts->feePaid; XRPL_ASSERT_PARTS( @@ -455,16 +451,16 @@ LoanPay::doApply() "payments add up"); // Decrease LoanBroker Debt by the amount paid, add the Loan value change - // (which might be negative). totalPaidToVaultForDebt may be negative, - // increasing the debt + // (which might be negative). debtTotalDelta may be negative, increasing the + // debt XRPL_ASSERT_PARTS( - isRounded(asset, totalPaidToVaultForDebt, loanScale), + isRounded(asset, debtTotalDelta, loanScale), "xrpl::LoanPay::doApply", - "totalPaidToVaultForDebt rounding good"); + "debtTotalDelta rounding good"); // Despite our best efforts, it's possible for rounding errors to accumulate // in the loan broker's debt total. This is because the broker may have more // than one loan with significantly different scales. - adjustImpreciseNumber(debtTotalProxy, -totalPaidToVaultForDebt, asset, vaultScale); + adjustImpreciseNumber(debtTotalProxy, -debtTotalDelta, asset, vaultScale); //------------------------------------------------------ // Vault object state changes @@ -490,7 +486,7 @@ LoanPay::doApply() #endif assetsAvailableProxy += totalPaidToVaultRounded; - assetsTotalProxy += paymentParts->valueChange; + assetsTotalProxy += assetsTotalDelta; XRPL_ASSERT_PARTS( *assetsAvailableProxy <= *assetsTotalProxy, @@ -543,11 +539,11 @@ LoanPay::doApply() return tecPRECISION_LOSS; // LCOV_EXCL_STOP } - if (paymentParts->valueChange != beast::kZero && assetsTotalAfter == assetsTotalBefore) + if (assetsTotalDelta != beast::kZero && assetsTotalAfter == assetsTotalBefore) { - // Non-zero valueChange with an unchanged assetsTotal indicates that the - // actual value change rounded to zero. That should be impossible, but I - // can't rule it out for extreme edge cases, so fail gracefully if it + // Non-zero assetsTotalDelta with an unchanged assetsTotal indicates that + // the actual value change rounded to zero. That should be impossible, but + // I can't rule it out for extreme edge cases, so fail gracefully if it // happens. // // LCOV_EXCL_START @@ -555,20 +551,21 @@ LoanPay::doApply() << "LoanPay: Vault assets expected change, but unchanged after rounding: " // << "Before: " << assetsTotalBefore // << ", After: " << assetsTotalAfter // - << ", ValueChange: " << paymentParts->valueChange; + << ", AssetsTotalDelta: " << assetsTotalDelta; return tecPRECISION_LOSS; // LCOV_EXCL_STOP } - if (paymentParts->valueChange == beast::kZero && assetsTotalAfter != assetsTotalBefore) + if (assetsTotalDelta == beast::kZero && assetsTotalAfter != assetsTotalBefore) { - // A change in assetsTotal when there was no valueChange indicates that - // something really weird happened. That should be flat out impossible. + // A change in assetsTotal when there was no assetsTotalDelta indicates + // that something really weird happened. That should be flat out + // impossible. // // LCOV_EXCL_START JLOG(j_.fatal()) << "LoanPay: Vault assets changed unexpectedly after rounding: " // << "Before: " << assetsTotalBefore // << ", After: " << assetsTotalAfter // - << ", ValueChange: " << paymentParts->valueChange; + << ", AssetsTotalDelta: " << assetsTotalDelta; return tecINTERNAL; // LCOV_EXCL_STOP } diff --git a/src/libxrpl/tx/transactors/lending/LoanSet.cpp b/src/libxrpl/tx/transactors/lending/LoanSet.cpp index 694d01c69f..bafadd7c1d 100644 --- a/src/libxrpl/tx/transactors/lending/LoanSet.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanSet.cpp @@ -439,12 +439,12 @@ LoanSet::doApply() principalRequested, properties.loanState.managementFeeDue); - auto const vaultMaximum = *vaultSle->at(sfAssetsMaximum); XRPL_ASSERT_PARTS( - vaultMaximum == 0 || vaultMaximum > *vaultTotalProxy, + *vaultSle->at(sfAssetsMaximum) == 0 || *vaultSle->at(sfAssetsMaximum) > *vaultTotalProxy, "xrpl::LoanSet::doApply", "Vault is below maximum limit"); - if (vaultMaximum != 0 && state.interestDue > vaultMaximum - vaultTotalProxy) + + if (loanOriginationExceedsVaultMaximum(vaultSle, vaultTotalProxy, state.interestDue)) { JLOG(j_.warn()) << "Loan would exceed the maximum assets of the vault"; return tecLIMIT_EXCEEDED; @@ -490,8 +490,9 @@ LoanSet::doApply() auto const loanAssetsToBorrower = principalRequested - originationFee; - auto const newDebtDelta = principalRequested + state.interestDue; - auto const newDebtTotal = brokerSle->at(sfDebtTotal) + newDebtDelta; + auto const [assetsTotalDelta, debtTotalDelta] = + loanOriginationDeltas(vaultSle, principalRequested, state.interestDue); + auto const newDebtTotal = brokerSle->at(sfDebtTotal) + debtTotalDelta; if (auto const debtMaximum = brokerSle->at(sfDebtMaximum); debtMaximum != 0 && debtMaximum < newDebtTotal) { @@ -634,7 +635,7 @@ LoanSet::doApply() // Update the balances in the vault vaultAvailableProxy -= principalRequested; - vaultTotalProxy += state.interestDue; + vaultTotalProxy += assetsTotalDelta; XRPL_ASSERT_PARTS( *vaultAvailableProxy <= *vaultTotalProxy, "xrpl::LoanSet::doApply", @@ -642,7 +643,7 @@ LoanSet::doApply() view.update(vaultSle); // Update the balances in the loan broker - adjustImpreciseNumber(brokerSle->at(sfDebtTotal), newDebtDelta, vaultAsset, vaultScale); + adjustImpreciseNumber(brokerSle->at(sfDebtTotal), debtTotalDelta, vaultAsset, vaultScale); adjustLoanBrokerOwnerCount(view, brokerSle, 1, j_); loanSequenceProxy += 1; // The sequence should be extremely unlikely to roll over, but fail if it diff --git a/src/libxrpl/tx/transactors/vault/VaultCreate.cpp b/src/libxrpl/tx/transactors/vault/VaultCreate.cpp index e1f5873a89..a522f62788 100644 --- a/src/libxrpl/tx/transactors/vault/VaultCreate.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultCreate.cpp @@ -30,6 +30,7 @@ #include #include #include +#include namespace xrpl { @@ -241,6 +242,8 @@ VaultCreate::doApply() } if (scale != 0u) vault->at(sfScale) = scale; + if (view().rules().enabled(featureLendingProtocolV1_1)) + vault->at(sfLEVersion) = std::to_underlying(VaultVersion::CashBasis); view().insert(vault); // Explicitly create MPToken for the vault owner diff --git a/src/test/app/LendingHelpers_test.cpp b/src/test/app/LendingHelpers_test.cpp index ac8e0764fc..1235920fab 100644 --- a/src/test/app/LendingHelpers_test.cpp +++ b/src/test/app/LendingHelpers_test.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -19,6 +20,7 @@ #include #include #include +#include #include namespace xrpl::test { @@ -1470,6 +1472,326 @@ class LendingHelpers_test : public beast::unit_test::Suite Number{-18304, -5})); } + void + testAccrualLoanOriginationDeltas() + { + using namespace xrpl::Accrual; + + struct TestCase + { + std::string name; + Number principalRequested; + Number interestDue; + }; + + auto const testCases = std::vector{ + {.name = "Zero interest", + .principalRequested = Number{1'000}, + .interestDue = Number{0}}, + {.name = "Nonzero interest", + .principalRequested = Number{1'000}, + .interestDue = Number{75}}, + }; + + for (auto const& tc : testCases) + { + testcase("Accrual::loanOriginationDeltas: " + tc.name); + + auto const deltas = loanOriginationDeltas(tc.principalRequested, tc.interestDue); + BEAST_EXPECTS( + deltas.assetsTotalDelta == tc.interestDue, + "assetsTotalDelta mismatch: expected " + to_string(tc.interestDue) + ", got " + + to_string(deltas.assetsTotalDelta)); + BEAST_EXPECTS( + deltas.debtTotalDelta == tc.principalRequested + tc.interestDue, + "debtTotalDelta mismatch: expected " + + to_string(tc.principalRequested + tc.interestDue) + ", got " + + to_string(deltas.debtTotalDelta)); + } + } + + void + testCashBasisLoanOriginationDeltas() + { + using namespace xrpl::CashBasis; + + testcase("CashBasis::loanOriginationDeltas: interestDue is ignored"); + + Number const principalRequested{1'000}; + Number const interestDue{75}; + + auto const deltas = loanOriginationDeltas(principalRequested); + BEAST_EXPECTS( + deltas.assetsTotalDelta == 0, + "assetsTotalDelta mismatch: expected 0, got " + to_string(deltas.assetsTotalDelta)); + BEAST_EXPECTS( + deltas.debtTotalDelta == principalRequested, + "debtTotalDelta mismatch: expected " + to_string(principalRequested) + ", got " + + to_string(deltas.debtTotalDelta)); + } + + void + testAccrualLoanOriginationExceedsVaultMaximum() + { + using namespace xrpl::Accrual; + + struct TestCase + { + std::string name; + Number vaultMaximum; + Number vaultTotal; + Number interestDue; + bool expected; + }; + + auto const testCases = std::vector{ + {.name = "No maximum configured", + .vaultMaximum = Number{0}, + .vaultTotal = Number{900}, + .interestDue = Number{1'000}, + .expected = false}, + {.name = "Interest fits under headroom", + .vaultMaximum = Number{1'000}, + .vaultTotal = Number{900}, + .interestDue = Number{50}, + .expected = false}, + {.name = "Interest exactly fills headroom", + .vaultMaximum = Number{1'000}, + .vaultTotal = Number{900}, + .interestDue = Number{100}, + .expected = false}, + {.name = "Interest exceeds headroom", + .vaultMaximum = Number{1'000}, + .vaultTotal = Number{900}, + .interestDue = Number{101}, + .expected = true}, + }; + + for (auto const& tc : testCases) + { + testcase("Accrual::loanOriginationExceedsVaultMaximum: " + tc.name); + BEAST_EXPECT( + loanOriginationExceedsVaultMaximum( + tc.vaultMaximum, tc.vaultTotal, tc.interestDue) == tc.expected); + } + } + + // Constructs a minimal ltLOAN SLE with just the fields needed by + // loanVaultExposure. Mirrors the bare-SLE pattern used by + // testCanApplyToBrokerCover for ltLOAN_BROKER. + static std::shared_ptr + makeLoanSle( + Number const& totalValueOutstanding, + Number const& principalOutstanding, + Number const& managementFeeOutstanding) + { + auto sle = std::make_shared(ltLOAN, uint256{1u}); + sle->at(sfTotalValueOutstanding) = totalValueOutstanding; + sle->at(sfPrincipalOutstanding) = principalOutstanding; + sle->at(sfManagementFeeOutstanding) = managementFeeOutstanding; + return sle; + } + + // Constructs a minimal ltVAULT SLE with just LEVersion set (or left + // absent), for exercising the dispatchers' per-Vault gating. + static std::shared_ptr + makeVaultSle( + std::optional leVersion = std::nullopt, + std::optional assetsMaximum = std::nullopt, + std::optional assetsTotal = std::nullopt) + { + auto sle = std::make_shared(ltVAULT, uint256{2u}); + if (leVersion) + sle->at(sfLEVersion) = std::to_underlying(*leVersion); + if (assetsMaximum) + sle->at(sfAssetsMaximum) = *assetsMaximum; + if (assetsTotal) + sle->at(sfAssetsTotal) = *assetsTotal; + return sle; + } + + void + testAccrualLoanVaultExposure() + { + testcase("Accrual::loanVaultExposure"); + + auto sle = makeLoanSle(Number{1'000}, Number{800}, Number{50}); + BEAST_EXPECT(xrpl::Accrual::loanVaultExposure(sle) == Number{950}); + } + + void + testCashBasisLoanVaultExposure() + { + testcase("CashBasis::loanVaultExposure"); + + auto sle = makeLoanSle(Number{1'000}, Number{800}, Number{50}); + BEAST_EXPECT(xrpl::CashBasis::loanVaultExposure(sle) == Number{800}); + } + + void + testLoanPaymentDeltas() + { + // principalPaid, interestPaid, feePaid, valueChange are all distinct + // and nonzero, with a nonzero valueChange simulating a late-payment + // penalty, so Accrual's formula is meaningfully exercised. + LoanPaymentParts const parts{ + .principalPaid = Number{100}, + .interestPaid = Number{20}, + .valueChange = Number{5}, + .feePaid = Number{3}}; + + { + testcase("Accrual::loanPaymentDeltas: nonzero valueChange"); + auto const deltas = xrpl::Accrual::loanPaymentDeltas(parts); + BEAST_EXPECT(deltas.assetsTotalDelta == parts.valueChange); + BEAST_EXPECT( + deltas.debtTotalDelta == + (parts.principalPaid + parts.interestPaid) - parts.valueChange); + } + + { + testcase("CashBasis::loanPaymentDeltas: nonzero valueChange ignored"); + auto const deltas = xrpl::CashBasis::loanPaymentDeltas(parts); + BEAST_EXPECT(deltas.assetsTotalDelta == parts.interestPaid); + BEAST_EXPECT(deltas.debtTotalDelta == parts.principalPaid); + } + } + + void + testLoanOriginationDeltasDispatcher() + { + using namespace jtx; + + Number const principalRequested{1'000}; + Number const interestDue{75}; + + auto const legacyVault = makeVaultSle(); + auto const cashBasisVault = makeVaultSle(VaultVersion::CashBasis); + + { + testcase( + "loanOriginationDeltas dispatcher: amendment enabled, legacy vault picks " + "Accrual"); + Env const env{*this}; + auto const deltas = loanOriginationDeltas(legacyVault, principalRequested, interestDue); + auto const expected = + xrpl::Accrual::loanOriginationDeltas(principalRequested, interestDue); + BEAST_EXPECT(deltas.assetsTotalDelta == expected.assetsTotalDelta); + BEAST_EXPECT(deltas.debtTotalDelta == expected.debtTotalDelta); + } + + { + testcase( + "loanOriginationDeltas dispatcher: amendment enabled, LEVersion == " + "VaultVersion::CashBasis picks CashBasis"); + Env const env{*this}; + auto const deltas = + loanOriginationDeltas(cashBasisVault, principalRequested, interestDue); + auto const expected = xrpl::CashBasis::loanOriginationDeltas(principalRequested); + BEAST_EXPECT(deltas.assetsTotalDelta == expected.assetsTotalDelta); + BEAST_EXPECT(deltas.debtTotalDelta == expected.debtTotalDelta); + } + } + + void + testLoanOriginationExceedsVaultMaximumDispatcher() + { + using namespace jtx; + + Number const vaultMaximum{1'000}; + Number const vaultTotal{900}; + // Exceeds Accrual's headroom (100), but must never trip CashBasis. + Number const interestDue{101}; + + auto const legacyVault = makeVaultSle(std::nullopt, vaultMaximum, vaultTotal); + auto const cashBasisVault = makeVaultSle(VaultVersion::CashBasis, vaultMaximum, vaultTotal); + + { + testcase( + "loanOriginationExceedsVaultMaximum dispatcher: amendment enabled, legacy vault " + "picks Accrual"); + Env const env{*this}; + BEAST_EXPECT( + loanOriginationExceedsVaultMaximum(legacyVault, vaultTotal, interestDue) == + xrpl::Accrual::loanOriginationExceedsVaultMaximum( + vaultMaximum, vaultTotal, interestDue)); + } + + { + testcase( + "loanOriginationExceedsVaultMaximum dispatcher: amendment enabled, LEVersion == " + "VaultVersion::CashBasis picks CashBasis"); + Env const env{*this}; + BEAST_EXPECT( + loanOriginationExceedsVaultMaximum(cashBasisVault, vaultTotal, interestDue) == + false); + } + } + + void + testLoanVaultExposureDispatcher() + { + using namespace jtx; + + auto const legacyVault = makeVaultSle(); + auto const cashBasisVault = makeVaultSle(VaultVersion::CashBasis); + + { + testcase("loanVaultExposure dispatcher: amendment enabled, legacy vault picks Accrual"); + Env const env{*this}; + auto sle = makeLoanSle(Number{1'000}, Number{800}, Number{50}); + BEAST_EXPECT( + loanVaultExposure(legacyVault, sle) == xrpl::Accrual::loanVaultExposure(sle)); + } + + { + testcase( + "loanVaultExposure dispatcher: amendment enabled, LEVersion == " + "VaultVersion::CashBasis " + "picks CashBasis"); + Env const env{*this}; + auto sle = makeLoanSle(Number{1'000}, Number{800}, Number{50}); + BEAST_EXPECT( + loanVaultExposure(cashBasisVault, sle) == xrpl::CashBasis::loanVaultExposure(sle)); + } + } + + void + testLoanPaymentDeltasDispatcher() + { + using namespace jtx; + + LoanPaymentParts const parts{ + .principalPaid = Number{100}, + .interestPaid = Number{20}, + .valueChange = Number{5}, + .feePaid = Number{3}}; + + auto const legacyVault = makeVaultSle(); + auto const cashBasisVault = makeVaultSle(VaultVersion::CashBasis); + + { + testcase("loanPaymentDeltas dispatcher: amendment enabled, legacy vault picks Accrual"); + Env const env{*this}; + auto const deltas = loanPaymentDeltas(legacyVault, parts); + auto const expected = xrpl::Accrual::loanPaymentDeltas(parts); + BEAST_EXPECT(deltas.assetsTotalDelta == expected.assetsTotalDelta); + BEAST_EXPECT(deltas.debtTotalDelta == expected.debtTotalDelta); + } + + { + testcase( + "loanPaymentDeltas dispatcher: amendment enabled, LEVersion == " + "VaultVersion::CashBasis " + "picks CashBasis"); + Env const env{*this}; + auto const deltas = loanPaymentDeltas(cashBasisVault, parts); + auto const expected = xrpl::CashBasis::loanPaymentDeltas(parts); + BEAST_EXPECT(deltas.assetsTotalDelta == expected.assetsTotalDelta); + BEAST_EXPECT(deltas.debtTotalDelta == expected.debtTotalDelta); + } + } + public: void testCanApplyToBrokerCover() @@ -1573,6 +1895,17 @@ public: testComputeOverpaymentComponents(); testComputeInterestAndFeeParts(); testCanApplyToBrokerCover(); + + testAccrualLoanOriginationDeltas(); + testCashBasisLoanOriginationDeltas(); + testAccrualLoanOriginationExceedsVaultMaximum(); + testAccrualLoanVaultExposure(); + testCashBasisLoanVaultExposure(); + testLoanPaymentDeltas(); + testLoanOriginationDeltasDispatcher(); + testLoanOriginationExceedsVaultMaximumDispatcher(); + testLoanVaultExposureDispatcher(); + testLoanPaymentDeltasDispatcher(); } }; diff --git a/src/test/app/Loan_test.cpp b/src/test/app/Loan_test.cpp index 231a3b405a..8a6f1669df 100644 --- a/src/test/app/Loan_test.cpp +++ b/src/test/app/Loan_test.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -92,8 +93,13 @@ class Loan_test : public beast::unit_test::Suite protected: // Ensure that all the features needed for Lending Protocol are included, // even if they are set to unsupported. - - FeatureBitset const all_{jtx::testableAmendments()}; + // + // featureLendingProtocolV1_1 is excluded from the default set: it changes + // Vault/LoanBroker accounting (AssetsTotal/DebtTotal/LossUnrealized), and + // most of this file's tests assert whole-life-specific expected values + // for those fields. Tests that specifically exercise the amendment opt + // it back in explicitly (e.g. `all_ | featureLendingProtocolV1_1`). + FeatureBitset const all_{jtx::testableAmendments() - featureLendingProtocolV1_1}; std::string const iouCurrency_{"IOU"}; void @@ -363,16 +369,21 @@ protected: { TenthBips16 const managementFeeRate{brokerSle->at(sfManagementFeeRate)}; auto const brokerDebt = brokerSle->at(sfDebtTotal); - auto const expectedDebt = principalOutstanding + interestOwed; - env.test.BEAST_EXPECT(brokerDebt == expectedDebt); - env.test.BEAST_EXPECT( - env.balance(pseudoAccount, broker.asset).number() == - brokerSle->at(sfCoverAvailable)); - env.test.BEAST_EXPECT(brokerSle->at(sfOwnerCount) == ownerCount); if (auto vaultSle = env.le(keylet::vault(brokerSle->at(sfVaultID))); env.test.BEAST_EXPECT(vaultSle)) { + auto const expectedDebt = + env.current()->rules().enabled(featureLendingProtocolV1_1) && + getVaultVersion(vaultSle) == VaultVersion::CashBasis + ? principalOutstanding + : principalOutstanding + interestOwed; + env.test.BEAST_EXPECT(brokerDebt == expectedDebt); + env.test.BEAST_EXPECT( + env.balance(pseudoAccount, broker.asset).number() == + brokerSle->at(sfCoverAvailable)); + env.test.BEAST_EXPECT(brokerSle->at(sfOwnerCount) == ownerCount); + Account const vaultPseudo{"vaultPseudoAccount", vaultSle->at(sfAccount)}; env.test.BEAST_EXPECT( vaultSle->at(sfAssetsAvailable) == @@ -468,7 +479,10 @@ protected: { env.test.BEAST_EXPECT( vaultSle->at(sfLossUnrealized) == - totalValue - managementFeeOutstanding); + (env.current()->rules().enabled(featureLendingProtocolV1_1) && + getVaultVersion(vaultSle) == VaultVersion::CashBasis + ? principalOutstanding + : totalValue - managementFeeOutstanding)); } else { @@ -635,8 +649,11 @@ protected: // log << vaultSle->getJson() << std::endl; auto const assetsUnavailable = vaultSle->at(sfAssetsTotal) - vaultSle->at(sfAssetsAvailable); - auto const unrealizedLoss = vaultSle->at(sfLossUnrealized) + state.totalValue - - state.managementFeeOutstanding; + auto const unrealizedLoss = vaultSle->at(sfLossUnrealized) + + (env.current()->rules().enabled(featureLendingProtocolV1_1) && + getVaultVersion(vaultSle) == VaultVersion::CashBasis + ? state.principalOutstanding + : state.totalValue - state.managementFeeOutstanding); if (!BEAST_EXPECT(unrealizedLoss <= assetsUnavailable)) { @@ -8547,6 +8564,972 @@ protected: }); } + // LendingProtocolV1_1 ("cash-basis" accounting) dedicated coverage. + // + // Existing tests never enable featureLendingProtocolV1_1 (see `all_` + // above), so these are the only tests in this file that exercise the + // amendment. They are called once, directly, from + // runAmendmentIndependent() -- not looped through + // runAmendmentSensitive()/amendmentCombinations(), since doing so would + // require re-deriving whole-life-specific expected values for ~15 + // unrelated regression tests. + + // 1. LoanSet origination: Vault.AssetsTotal/LoanBroker.DebtTotal deltas, + // and the AssetsMaximum/DebtMaximum guards (which always check against + // principal + interestDue, regardless of the amendment). + void + testCashBasisLoanSetOrigination() + { + testcase("cash-basis: LoanSet origination"); + + using namespace jtx; + using namespace loan; + + PrettyAsset const xrpAsset{xrpIssue(), 1'000'000}; + BrokerParameters const brokerParams{ + .vaultDeposit = 100'000, + .debtMax = 0, + .coverRateMin = TenthBips32{0}, + .coverDeposit = 0, + .managementFeeRate = TenthBips16{0}, + .coverRateLiquidation = TenthBips32{0}}; + + Number const principalRequest{10'000}; + TenthBips32 const interestRate{percentageToTenthBips(10)}; + std::uint32_t const paymentTotal = 2; + std::uint32_t const paymentInterval = 86400; + + // Creates a broker/vault, submits a single LoanSet with a nonzero + // interest rate, and returns the observed Vault.AssetsTotal / + // LoanBroker.DebtTotal deltas plus the loan's own computed + // interestDue and principalOutstanding. + auto runOrigination = [&](FeatureBitset features) { + Env env(*this, features); + + Account const lender{"lender"}; + Account const borrower{"borrower"}; + env.fund(XRP(1'000'000), lender, borrower); + env.close(); + + BrokerInfo const broker{createVaultAndBroker(env, xrpAsset, lender, brokerParams)}; + + auto const vaultBefore = env.le(broker.vaultKeylet()); + auto const brokerBefore = env.le(broker.brokerKeylet()); + BEAST_EXPECT(vaultBefore && brokerBefore); + Number const assetsTotalBefore = vaultBefore->at(sfAssetsTotal); + Number const debtTotalBefore = brokerBefore->at(sfDebtTotal); + + auto const loanSequence = brokerBefore->at(sfLoanSequence); + auto const loanKeylet = keylet::loan(broker.brokerID, loanSequence); + + env(set(borrower, broker.brokerID, xrpAsset(principalRequest).value()), + kCounterparty(lender), + kInterestRate(interestRate), + kPaymentTotal(paymentTotal), + kPaymentInterval(paymentInterval), + Sig(sfCounterpartySignature, lender), + Fee(env.current()->fees().base * 2), + Ter(tesSUCCESS)); + env.close(); + + auto const loanSle = env.le(loanKeylet); + BEAST_EXPECT(loanSle); + Number const principalOutstanding = loanSle->at(sfPrincipalOutstanding); + Number const totalValueOutstanding = loanSle->at(sfTotalValueOutstanding); + Number const interestDue = totalValueOutstanding - principalOutstanding; + BEAST_EXPECT(interestDue > beast::kZero); + BEAST_EXPECT(principalOutstanding == xrpAsset(principalRequest).value()); + + auto const vaultAfter = env.le(broker.vaultKeylet()); + auto const brokerAfter = env.le(broker.brokerKeylet()); + BEAST_EXPECT(vaultAfter && brokerAfter); + Number const assetsTotalDelta = + Number(vaultAfter->at(sfAssetsTotal)) - assetsTotalBefore; + Number const debtTotalDelta = Number(brokerAfter->at(sfDebtTotal)) - debtTotalBefore; + + return std::make_tuple( + assetsTotalDelta, debtTotalDelta, interestDue, principalOutstanding); + }; + + Number interestDueCash{}; + Number principalOutstandingCash{}; + { + auto const [assetsTotalDelta, debtTotalDelta, interestDue, principalOutstanding] = + runOrigination(all_ | featureLendingProtocolV1_1); + interestDueCash = interestDue; + principalOutstandingCash = principalOutstanding; + + BEAST_EXPECTS( + assetsTotalDelta == beast::kZero, + "cash-basis origination must not change AssetsTotal; delta=" + + to_string(assetsTotalDelta)); + BEAST_EXPECTS( + debtTotalDelta == principalOutstanding, + "cash-basis origination must add principal-only to DebtTotal; delta=" + + to_string(debtTotalDelta) + " principal=" + to_string(principalOutstanding)); + } + + { + auto const [assetsTotalDelta, debtTotalDelta, interestDue, principalOutstanding] = + runOrigination(all_); + + BEAST_EXPECTS( + assetsTotalDelta == interestDue, + "whole-life origination must add interestDue to AssetsTotal; delta=" + + to_string(assetsTotalDelta) + " interestDue=" + to_string(interestDue)); + BEAST_EXPECTS( + debtTotalDelta == principalOutstanding + interestDue, + "whole-life origination must add principal+interest to DebtTotal; delta=" + + to_string(debtTotalDelta)); + } + + // AssetsMaximum guard checks interestDue headroom only under + // whole-life accounting; DebtMaximum guard also varies by model. + auto runVaultGuard = [&](FeatureBitset features, Number const& slack, TER expected) { + Env env(*this, features); + + Account const lender{"lender"}; + Account const borrower{"borrower"}; + env.fund(XRP(1'000'000), lender, borrower); + env.close(); + + BrokerInfo const broker{createVaultAndBroker(env, xrpAsset, lender, brokerParams)}; + + auto const vaultSle = env.le(broker.vaultKeylet()); + BEAST_EXPECT(vaultSle); + Number const assetsTotalBefore = vaultSle->at(sfAssetsTotal); + + Vault const vault{env}; + auto tx = vault.set({.owner = lender, .id = broker.vaultID}); + tx[sfAssetsMaximum] = assetsTotalBefore + slack; + env(tx); + env.close(); + + env(set(borrower, broker.brokerID, xrpAsset(principalRequest).value()), + kCounterparty(lender), + kInterestRate(interestRate), + kPaymentTotal(paymentTotal), + kPaymentInterval(paymentInterval), + Sig(sfCounterpartySignature, lender), + Fee(env.current()->fees().base * 2), + Ter(expected)); + env.close(); + }; + + auto runBrokerGuard = [&](FeatureBitset features, Number const& debtMaximum, TER expected) { + Env env(*this, features); + + Account const lender{"lender"}; + Account const borrower{"borrower"}; + env.fund(XRP(1'000'000), lender, borrower); + env.close(); + + BrokerInfo const broker{createVaultAndBroker(env, xrpAsset, lender, brokerParams)}; + + env(loanBroker::set(lender, broker.vaultID), + loanBroker::kLoanBrokerId(broker.brokerID), + loanBroker::kDebtMaximum(debtMaximum), + Fee(env.current()->fees().base * 2)); + env.close(); + + env(set(borrower, broker.brokerID, xrpAsset(principalRequest).value()), + kCounterparty(lender), + kInterestRate(interestRate), + kPaymentTotal(paymentTotal), + kPaymentInterval(paymentInterval), + Sig(sfCounterpartySignature, lender), + Fee(env.current()->fees().base * 2), + Ter(expected)); + env.close(); + }; + + Number const oneDrop = xrpAsset(1).value(); + { + testcase("whole-life: LoanSet AssetsMaximum guard checks interestDue headroom"); + // Guard rejects when there's not quite enough headroom for the + // interest. + runVaultGuard(all_, interestDueCash - oneDrop, tecLIMIT_EXCEEDED); + // Guard accepts at the exact boundary. + runVaultGuard(all_, interestDueCash, tesSUCCESS); + } + + { + testcase("cash-basis: LoanSet AssetsMaximum guard ignores interestDue headroom"); + // Even far less headroom than interestDue still succeeds, since + // cash-basis origination never adds interest to AssetsTotal. + runVaultGuard(all_ | featureLendingProtocolV1_1, oneDrop, tesSUCCESS); + } + + // DebtMaximum guard: cash-basis projects principal-only DebtTotal; + // whole-life projects principal + interestDue. + for (auto const cashBasis : {true, false}) + { + testcase( + std::string("LoanSet DebtMaximum guard (") + + (cashBasis ? "cash-basis)" : "whole-life)")); + auto const features = cashBasis ? all_ | featureLendingProtocolV1_1 : all_; + Number const newDebtTotal = + principalOutstandingCash + (cashBasis ? Number{} : interestDueCash); + runBrokerGuard(features, newDebtTotal - oneDrop, tecLIMIT_EXCEEDED); + runBrokerGuard(features, newDebtTotal, tesSUCCESS); + } + } + + // 2. LoanPay: regular, late, overpayment, and full-payment types. + // Assert Vault.AssetsTotal/LoanBroker.DebtTotal deltas match + // interestPaid/principalPaid under cash-basis, and cross-check the + // amendment-disabled run's deltas against the documented whole-life + // formula (AssetsTotal += valueChange; DebtTotal mirrors the loan's own + // TotalValueOutstanding delta exactly, since whole-life debt recognition + // tracks total loan value). + void + testCashBasisLoanPay() + { + using namespace jtx; + using namespace loan; + using namespace std::chrono_literals; + using tp = NetClock::time_point; + + PrettyAsset const xrpAsset{xrpIssue(), 1'000'000}; + BrokerParameters const brokerParams{ + .vaultDeposit = 1'000'000, + .debtMax = 0, + .coverRateMin = TenthBips32{0}, + .coverDeposit = 0, + .managementFeeRate = TenthBips16{0}, + .coverRateLiquidation = TenthBips32{0}}; + + Number const principalRequest{12'000}; + TenthBips32 const interestRate{percentageToTenthBips(12)}; + std::uint32_t const paymentTotal = 4; + std::uint32_t const paymentInterval = 600; + std::uint32_t const gracePeriod = 300; + + struct PaymentDeltas + { + Number principalPaid; + Number assetsTotalDelta; + Number debtTotalDelta; + Number totalValueDelta; + }; + + // Sets up a fresh broker + loan, advances time, submits a single + // payment of the given type/amount, and returns the observed deltas. + auto runPayment = [&](FeatureBitset features, + std::uint32_t loanSetFlags, + std::uint32_t payFlags, + std::function const& advanceTime, + std::function const& paymentAmount) { + Env env(*this, features); + + Account const lender{"lender"}; + Account const borrower{"borrower"}; + env.fund(XRP(10'000'000), lender, borrower); + env.close(); + + BrokerInfo const broker{createVaultAndBroker(env, xrpAsset, lender, brokerParams)}; + + LoanParameters const loanParams{ + .account = borrower, + .counter = lender, + .principalRequest = principalRequest, + .interest = interestRate, + .payTotal = paymentTotal, + .payInterval = paymentInterval, + .gracePd = gracePeriod, + .flags = loanSetFlags, + }; + + auto const brokerBeforeLoan = env.le(broker.brokerKeylet()); + BEAST_EXPECT(brokerBeforeLoan); + auto const loanSequence = brokerBeforeLoan->at(sfLoanSequence); + auto const loanKeylet = keylet::loan(broker.brokerID, loanSequence); + + env(loanParams(env, broker)); + env.close(); + + LoanState const state = getCurrentState(env, broker, loanKeylet); + + advanceTime(env, state.startDate); + + auto const vaultBefore = env.le(broker.vaultKeylet()); + auto const brokerBefore = env.le(broker.brokerKeylet()); + auto const loanBefore = env.le(loanKeylet); + BEAST_EXPECT(vaultBefore && brokerBefore && loanBefore); + + Number const principalBefore = loanBefore->at(sfPrincipalOutstanding); + Number const totalValueBefore = loanBefore->at(sfTotalValueOutstanding); + Number const assetsTotalBefore = vaultBefore->at(sfAssetsTotal); + Number const debtTotalBefore = brokerBefore->at(sfDebtTotal); + + STAmount const amount = paymentAmount(state); + env(pay(borrower, loanKeylet.key, amount, payFlags), Ter(tesSUCCESS)); + env.close(); + + auto const vaultAfter = env.le(broker.vaultKeylet()); + auto const brokerAfter = env.le(broker.brokerKeylet()); + auto const loanAfter = env.le(loanKeylet); + BEAST_EXPECT(vaultAfter && brokerAfter && loanAfter); + + Number const principalAfter = loanAfter->at(sfPrincipalOutstanding); + Number const totalValueAfter = loanAfter->at(sfTotalValueOutstanding); + Number const assetsTotalAfter = vaultAfter->at(sfAssetsTotal); + Number const debtTotalAfter = brokerAfter->at(sfDebtTotal); + + return PaymentDeltas{ + .principalPaid = principalBefore - principalAfter, + .assetsTotalDelta = assetsTotalAfter - assetsTotalBefore, + .debtTotalDelta = debtTotalAfter - debtTotalBefore, + .totalValueDelta = totalValueAfter - totalValueBefore}; + }; + + // Compares the disabled (whole-life) and enabled (cash-basis) runs + // of the same payment scenario, and asserts the documented + // relationships between them. + auto checkScenario = [&](std::string const& label, + PaymentDeltas const& off, + PaymentDeltas const& on) { + testcase("cash-basis: LoanPay " + label); + + // The loan's own PrincipalOutstanding field is untouched by + // the amendment. + BEAST_EXPECTS( + off.principalPaid == on.principalPaid, + "principalPaid must be amendment-independent; off=" + to_string(off.principalPaid) + + " on=" + to_string(on.principalPaid)); + + // Whole-life structural invariant: DebtTotal (which + // recognizes a loan's full remaining value as debt) must + // change exactly as the loan's own TotalValueOutstanding + // does. + BEAST_EXPECTS( + off.debtTotalDelta == off.totalValueDelta, + "whole-life DebtTotal delta must mirror TotalValueOutstanding delta; " + "debtTotalDelta=" + + to_string(off.debtTotalDelta) + + " totalValueDelta=" + to_string(off.totalValueDelta)); + + // Derive interestPaid from the whole-life run's independent + // ledger deltas: + // assetsTotalDelta_off == valueChange + // debtTotalDelta_off == valueChange - (principalPaid + interestPaid) + // => interestPaid == assetsTotalDelta_off - debtTotalDelta_off - principalPaid + Number const interestPaid = + off.assetsTotalDelta - off.debtTotalDelta - off.principalPaid; + BEAST_EXPECTS( + interestPaid >= beast::kZero, + "derived interestPaid must be non-negative: " + to_string(interestPaid)); + + BEAST_EXPECTS( + on.assetsTotalDelta == interestPaid, + "cash-basis AssetsTotal delta must equal interestPaid; delta=" + + to_string(on.assetsTotalDelta) + " interestPaid=" + to_string(interestPaid)); + BEAST_EXPECTS( + on.debtTotalDelta == -on.principalPaid, + "cash-basis DebtTotal delta must equal -principalPaid; delta=" + + to_string(on.debtTotalDelta) + " principalPaid=" + to_string(on.principalPaid)); + }; + + // ---- Regular, on-time payment ---- + { + auto const noAdvance = [](Env& env, tp const&) { env.close(); }; + auto const regularAmount = [&](LoanState const& state) { + return STAmount{ + xrpAsset, + roundPeriodicPayment(xrpAsset, state.periodicPayment, state.loanScale) * + Number{3, -1} * 5}; // 1.5x, so only a single period is paid + }; + + auto const off = runPayment(all_, 0, 0, noAdvance, regularAmount); + auto const on = + runPayment(all_ | featureLendingProtocolV1_1, 0, 0, noAdvance, regularAmount); + + // Regular, on-time payments never change the loan's value beyond + // normal amortization (production asserts valueChange == 0), so + // AssetsTotal must be unaffected in the whole-life run. + BEAST_EXPECTS( + off.assetsTotalDelta == beast::kZero, + "regular on-time payment must not change AssetsTotal under whole-life; delta=" + + to_string(off.assetsTotalDelta)); + + checkScenario("regular payment", off, on); + } + + // ---- Late payment ---- + { + auto const advancePastDue = [&](Env& env, tp const& startDate) { + env.close(startDate + std::chrono::seconds(paymentInterval + 1)); + }; + auto const lateAmount = [&](LoanState const& state) { + return STAmount{ + xrpAsset, + roundPeriodicPayment(xrpAsset, state.periodicPayment, state.loanScale) * + Number{3}}; // generous; excess is not withdrawn + }; + + auto const off = runPayment(all_, 0, tfLoanLatePayment, advancePastDue, lateAmount); + auto const on = runPayment( + all_ | featureLendingProtocolV1_1, + 0, + tfLoanLatePayment, + advancePastDue, + lateAmount); + + checkScenario("late payment", off, on); + } + + // ---- Overpayment ---- + { + auto const noAdvance = [](Env& env, tp const&) { env.close(); }; + auto const overpayAmount = [&](LoanState const& state) { + // One regular period, plus a generous extra principal + // paydown. + return STAmount{ + xrpAsset, + roundPeriodicPayment(xrpAsset, state.periodicPayment, state.loanScale) + + xrpAsset(2'000).value()}; + }; + + auto const off = + runPayment(all_, tfLoanOverpayment, tfLoanOverpayment, noAdvance, overpayAmount); + auto const on = runPayment( + all_ | featureLendingProtocolV1_1, + tfLoanOverpayment, + tfLoanOverpayment, + noAdvance, + overpayAmount); + + checkScenario("overpayment", off, on); + } + + // ---- Full payment ---- + { + auto const noAdvance = [](Env& env, tp const&) { env.close(); }; + auto const fullAmount = [&](LoanState const&) { + // Generously large: full payment only ever consumes exactly + // what's due (principal + accrued interest; close fee/ + // prepayment penalty are 0 here), excess is not withdrawn. + return STAmount{xrpAsset, xrpAsset(principalRequest).value() * Number{2}}; + }; + + auto const off = runPayment(all_, 0, tfLoanFullPayment, noAdvance, fullAmount); + auto const on = runPayment( + all_ | featureLendingProtocolV1_1, 0, tfLoanFullPayment, noAdvance, fullAmount); + + checkScenario("full payment", off, on); + } + } + + // 3. LoanManage: impair, unimpair, and default. + void + testCashBasisLoanManage() + { + using namespace jtx; + using namespace loan; + using namespace std::chrono_literals; + + PrettyAsset const xrpAsset{xrpIssue(), 1'000'000}; + BrokerParameters const brokerParams{ + .vaultDeposit = 1'000'000, + .debtMax = 0, + .coverRateMin = TenthBips32{percentageToTenthBips(10)}, + .coverDeposit = 5'000, + .managementFeeRate = TenthBips16{0}, + .coverRateLiquidation = TenthBips32{percentageToTenthBips(25)}}; + + Number const principalRequest{10'000}; + TenthBips32 const interestRate{percentageToTenthBips(12)}; + std::uint32_t const paymentTotal = 4; + std::uint32_t const paymentInterval = 600; + std::uint32_t const gracePeriod = 60; + + auto setupLoan = [&](Env& env) { + Account const lender{"lender"}; + Account const borrower{"borrower"}; + env.fund(XRP(10'000'000), lender, borrower); + env.close(); + + BrokerInfo const broker{createVaultAndBroker(env, xrpAsset, lender, brokerParams)}; + + LoanParameters const loanParams{ + .account = borrower, + .counter = lender, + .principalRequest = principalRequest, + .interest = interestRate, + .payTotal = paymentTotal, + .payInterval = paymentInterval, + .gracePd = gracePeriod, + }; + + auto const brokerBeforeLoan = env.le(broker.brokerKeylet()); + BEAST_EXPECT(brokerBeforeLoan); + auto const loanSequence = brokerBeforeLoan->at(sfLoanSequence); + auto const loanKeylet = keylet::loan(broker.brokerID, loanSequence); + + env(loanParams(env, broker)); + env.close(); + + return std::make_tuple(broker, loanKeylet, lender, borrower); + }; + + // ---- impair / unimpair ---- + auto runImpairUnimpair = [&](FeatureBitset features) { + Env env(*this, features); + auto const [broker, loanKeylet, lender, borrower] = setupLoan(env); + + auto const loanBefore = env.le(loanKeylet); + BEAST_EXPECT(loanBefore); + Number const principalOutstanding = loanBefore->at(sfPrincipalOutstanding); + Number const totalValueOutstanding = loanBefore->at(sfTotalValueOutstanding); + Number const managementFeeOutstanding = loanBefore->at(sfManagementFeeOutstanding); + + Number const expectedExposure = + env.current()->rules().enabled(featureLendingProtocolV1_1) + ? principalOutstanding + : totalValueOutstanding - managementFeeOutstanding; + + auto const vaultBeforeImpair = env.le(broker.vaultKeylet()); + BEAST_EXPECT(vaultBeforeImpair); + Number const lossBefore = vaultBeforeImpair->at(sfLossUnrealized); + + env(manage(lender, loanKeylet.key, tfLoanImpair), Ter(tesSUCCESS)); + env.close(); + + auto const vaultAfterImpair = env.le(broker.vaultKeylet()); + BEAST_EXPECT(vaultAfterImpair); + Number const impairDelta = Number(vaultAfterImpair->at(sfLossUnrealized)) - lossBefore; + + env(manage(lender, loanKeylet.key, tfLoanUnimpair), Ter(tesSUCCESS)); + env.close(); + + auto const vaultAfterUnimpair = env.le(broker.vaultKeylet()); + BEAST_EXPECT(vaultAfterUnimpair); + Number const netDelta = Number(vaultAfterUnimpair->at(sfLossUnrealized)) - lossBefore; + + return std::make_tuple(expectedExposure, impairDelta, netDelta); + }; + + for (auto const features : {all_ | featureLendingProtocolV1_1, all_}) + { + testcase( + std::string("cash-basis: LoanManage impair/unimpair (") + + (features[featureLendingProtocolV1_1] ? "enabled)" : "disabled)")); + auto const [expectedExposure, impairDelta, netDelta] = runImpairUnimpair(features); + + BEAST_EXPECTS( + impairDelta == expectedExposure, + "impair must add loanVaultExposure to LossUnrealized; delta=" + + to_string(impairDelta) + " expected=" + to_string(expectedExposure)); + BEAST_EXPECTS( + netDelta == beast::kZero, + "unimpair must be an exact reversal of impair; net=" + to_string(netDelta)); + } + + // ---- impair, then default ---- + auto runDefault = [&](FeatureBitset features) { + Env env(*this, features); + auto const [broker, loanKeylet, lender, borrower] = setupLoan(env); + + auto const loanBeforeImpair = env.le(loanKeylet); + BEAST_EXPECT(loanBeforeImpair); + Number const principalOutstanding = loanBeforeImpair->at(sfPrincipalOutstanding); + Number const totalValueOutstanding = loanBeforeImpair->at(sfTotalValueOutstanding); + Number const managementFeeOutstanding = + loanBeforeImpair->at(sfManagementFeeOutstanding); + + Number const expectedExposure = + env.current()->rules().enabled(featureLendingProtocolV1_1) + ? principalOutstanding + : totalValueOutstanding - managementFeeOutstanding; + + env(manage(lender, loanKeylet.key, tfLoanImpair), Ter(tesSUCCESS)); + env.close(); + + LoanState const state = getCurrentState(env, broker, loanKeylet); + env.close( + state.startDate + std::chrono::seconds(paymentInterval) + + std::chrono::seconds(gracePeriod) + 60s); + + auto const vaultBefore = env.le(broker.vaultKeylet()); + auto const brokerBefore = env.le(broker.brokerKeylet()); + BEAST_EXPECT(vaultBefore && brokerBefore); + Number const assetsTotalBefore = vaultBefore->at(sfAssetsTotal); + Number const debtTotalBefore = brokerBefore->at(sfDebtTotal); + Number const lossBefore = vaultBefore->at(sfLossUnrealized); + Number const coverAvailableBefore = brokerBefore->at(sfCoverAvailable); + + env(manage(lender, loanKeylet.key, tfLoanDefault), Ter(tesSUCCESS)); + env.close(); + + auto const vaultAfter = env.le(broker.vaultKeylet()); + auto const brokerAfter = env.le(broker.brokerKeylet()); + BEAST_EXPECT(vaultAfter && brokerAfter); + Number const assetsTotalDelta = + Number(vaultAfter->at(sfAssetsTotal)) - assetsTotalBefore; + Number const debtTotalDelta = Number(brokerAfter->at(sfDebtTotal)) - debtTotalBefore; + Number const lossDelta = Number(vaultAfter->at(sfLossUnrealized)) - lossBefore; + Number const coverAvailableDelta = + Number(brokerAfter->at(sfCoverAvailable)) - coverAvailableBefore; + + Number const defaultCovered = -coverAvailableDelta; + Number const vaultDefaultAmount = expectedExposure - defaultCovered; + + return std::make_tuple( + expectedExposure, assetsTotalDelta, debtTotalDelta, lossDelta, vaultDefaultAmount); + }; + + for (auto const features : {all_ | featureLendingProtocolV1_1, all_}) + { + testcase( + std::string("cash-basis: LoanManage default (") + + (features[featureLendingProtocolV1_1] ? "enabled)" : "disabled)")); + auto const + [expectedExposure, + assetsTotalDelta, + debtTotalDelta, + lossDelta, + vaultDefaultAmount] = runDefault(features); + + BEAST_EXPECTS( + debtTotalDelta == -expectedExposure, + "default must reduce DebtTotal by the unified default amount; delta=" + + to_string(debtTotalDelta) + " expected=" + to_string(expectedExposure)); + BEAST_EXPECTS( + lossDelta == -expectedExposure, + "default must reverse the earlier impair's LossUnrealized exactly; delta=" + + to_string(lossDelta) + " expected=" + to_string(expectedExposure)); + BEAST_EXPECTS( + assetsTotalDelta == -vaultDefaultAmount, + "default must reduce AssetsTotal by (defaultAmount - defaultCovered); delta=" + + to_string(assetsTotalDelta) + " expected=" + to_string(-vaultDefaultAmount)); + } + } + + // 3b. LEVersion regression: a Vault created before featureLendingProtocolV1_1 + // activates (LEVersion absent) must keep whole-life (accrual) accounting + // forever, even after the amendment is later enabled -- the switch is + // per-Vault (LEVersion == VaultVersion::CashBasis), not a single global amendment + // flag. + void + testLegacyVaultKeepsAccrualAfterAmendmentEnabled() + { + testcase("LEVersion: legacy vault keeps accrual after amendment enabled"); + + using namespace jtx; + using namespace loan; + using namespace std::chrono_literals; + + PrettyAsset const xrpAsset{xrpIssue(), 1'000'000}; + BrokerParameters const brokerParams{ + .vaultDeposit = 1'000'000, + .debtMax = 0, + .coverRateMin = TenthBips32{percentageToTenthBips(10)}, + .coverDeposit = 5'000, + .managementFeeRate = TenthBips16{0}, + .coverRateLiquidation = TenthBips32{percentageToTenthBips(25)}}; + + Number const principalRequest{10'000}; + TenthBips32 const interestRate{percentageToTenthBips(12)}; + std::uint32_t const paymentTotal = 4; + std::uint32_t const paymentInterval = 600; + std::uint32_t const gracePeriod = 60; + + // Amendment disabled at Vault creation time: LEVersion stays absent. + Env env(*this, all_); + + Account const lender{"lender"}; + Account const borrower{"borrower"}; + env.fund(XRP(10'000'000), lender, borrower); + env.close(); + + BrokerInfo const broker{createVaultAndBroker(env, xrpAsset, lender, brokerParams)}; + + { + auto const vaultSle = env.le(broker.vaultKeylet()); + BEAST_EXPECT(vaultSle); + BEAST_EXPECT(!vaultSle->isFieldPresent(sfLEVersion)); + } + + // Now enable the amendment -- production dispatch must still treat + // this specific Vault as accrual-basis, since its LEVersion is + // (and remains) absent. + env.enableFeature(featureLendingProtocolV1_1); + env.close(); + + LoanParameters const loanParams{ + .account = borrower, + .counter = lender, + .principalRequest = principalRequest, + .interest = interestRate, + .payTotal = paymentTotal, + .payInterval = paymentInterval, + .gracePd = gracePeriod, + }; + + auto const brokerBeforeLoan = env.le(broker.brokerKeylet()); + BEAST_EXPECT(brokerBeforeLoan); + auto const loanSequence = brokerBeforeLoan->at(sfLoanSequence); + auto const loanKeylet = keylet::loan(broker.brokerID, loanSequence); + + // ---- LoanSet origination: whole-life formulas expected ---- + auto const vaultBeforeSet = env.le(broker.vaultKeylet()); + auto const brokerBeforeSet = env.le(broker.brokerKeylet()); + BEAST_EXPECT(vaultBeforeSet && brokerBeforeSet); + Number const assetsTotalBeforeSet = vaultBeforeSet->at(sfAssetsTotal); + Number const debtTotalBeforeSet = brokerBeforeSet->at(sfDebtTotal); + + env(loanParams(env, broker)); + env.close(); + + auto const loanAfterSet = env.le(loanKeylet); + BEAST_EXPECT(loanAfterSet); + Number const principalOutstanding = loanAfterSet->at(sfPrincipalOutstanding); + Number const totalValueOutstanding = loanAfterSet->at(sfTotalValueOutstanding); + Number const interestDue = totalValueOutstanding - principalOutstanding; + BEAST_EXPECT(interestDue > beast::kZero); + + auto const vaultAfterSet = env.le(broker.vaultKeylet()); + auto const brokerAfterSet = env.le(broker.brokerKeylet()); + BEAST_EXPECT(vaultAfterSet && brokerAfterSet); + Number const assetsTotalDeltaSet = + Number(vaultAfterSet->at(sfAssetsTotal)) - assetsTotalBeforeSet; + Number const debtTotalDeltaSet = + Number(brokerAfterSet->at(sfDebtTotal)) - debtTotalBeforeSet; + + BEAST_EXPECTS( + assetsTotalDeltaSet == interestDue, + "legacy vault origination must still add interestDue to AssetsTotal; delta=" + + to_string(assetsTotalDeltaSet) + " interestDue=" + to_string(interestDue)); + BEAST_EXPECTS( + debtTotalDeltaSet == principalOutstanding + interestDue, + "legacy vault origination must still add principal+interest to DebtTotal; delta=" + + to_string(debtTotalDeltaSet)); + + LoanState const state = getCurrentState(env, broker, loanKeylet); + env.close(); + + // ---- LoanPay: whole-life formulas expected ---- + auto const vaultBeforePay = env.le(broker.vaultKeylet()); + auto const brokerBeforePay = env.le(broker.brokerKeylet()); + auto const loanBeforePay = env.le(loanKeylet); + BEAST_EXPECT(vaultBeforePay && brokerBeforePay && loanBeforePay); + Number const totalValueBeforePay = loanBeforePay->at(sfTotalValueOutstanding); + Number const assetsTotalBeforePay = vaultBeforePay->at(sfAssetsTotal); + Number const debtTotalBeforePay = brokerBeforePay->at(sfDebtTotal); + + STAmount const paymentAmount{ + xrpAsset, roundPeriodicPayment(xrpAsset, state.periodicPayment, state.loanScale)}; + env(pay(borrower, loanKeylet.key, paymentAmount), Ter(tesSUCCESS)); + env.close(); + + auto const vaultAfterPay = env.le(broker.vaultKeylet()); + auto const brokerAfterPay = env.le(broker.brokerKeylet()); + auto const loanAfterPay = env.le(loanKeylet); + BEAST_EXPECT(vaultAfterPay && brokerAfterPay && loanAfterPay); + Number const totalValueAfterPay = loanAfterPay->at(sfTotalValueOutstanding); + Number const assetsTotalDeltaPay = + Number(vaultAfterPay->at(sfAssetsTotal)) - assetsTotalBeforePay; + Number const debtTotalDeltaPay = + Number(brokerAfterPay->at(sfDebtTotal)) - debtTotalBeforePay; + Number const totalValueDeltaPay = totalValueAfterPay - totalValueBeforePay; + + // A regular, on-time payment has valueChange == 0, so whole-life + // AssetsTotal is untouched and DebtTotal mirrors TotalValueOutstanding. + BEAST_EXPECTS( + assetsTotalDeltaPay == beast::kZero, + "legacy vault regular payment must not change AssetsTotal; delta=" + + to_string(assetsTotalDeltaPay)); + BEAST_EXPECTS( + debtTotalDeltaPay == totalValueDeltaPay, + "legacy vault DebtTotal delta must mirror TotalValueOutstanding delta; " + "debtTotalDelta=" + + to_string(debtTotalDeltaPay) + " totalValueDelta=" + to_string(totalValueDeltaPay)); + + // ---- LoanManage: impair, then default -- whole-life exposure expected ---- + auto const loanBeforeImpair = env.le(loanKeylet); + BEAST_EXPECT(loanBeforeImpair); + Number const totalValueBeforeImpair = loanBeforeImpair->at(sfTotalValueOutstanding); + Number const managementFeeBeforeImpair = loanBeforeImpair->at(sfManagementFeeOutstanding); + Number const expectedExposure = totalValueBeforeImpair - managementFeeBeforeImpair; + + env(manage(lender, loanKeylet.key, tfLoanImpair), Ter(tesSUCCESS)); + env.close(); + + LoanState const stateAtImpair = getCurrentState(env, broker, loanKeylet); + env.close( + stateAtImpair.startDate + std::chrono::seconds(paymentInterval) + + std::chrono::seconds(gracePeriod) + 60s); + + auto const vaultBeforeDefault = env.le(broker.vaultKeylet()); + auto const brokerBeforeDefault = env.le(broker.brokerKeylet()); + BEAST_EXPECT(vaultBeforeDefault && brokerBeforeDefault); + Number const debtTotalBeforeDefault = brokerBeforeDefault->at(sfDebtTotal); + Number const lossBeforeDefault = vaultBeforeDefault->at(sfLossUnrealized); + + env(manage(lender, loanKeylet.key, tfLoanDefault), Ter(tesSUCCESS)); + env.close(); + + auto const vaultAfterDefault = env.le(broker.vaultKeylet()); + auto const brokerAfterDefault = env.le(broker.brokerKeylet()); + BEAST_EXPECT(vaultAfterDefault && brokerAfterDefault); + Number const debtTotalDeltaDefault = + Number(brokerAfterDefault->at(sfDebtTotal)) - debtTotalBeforeDefault; + Number const lossDeltaDefault = + Number(vaultAfterDefault->at(sfLossUnrealized)) - lossBeforeDefault; + + BEAST_EXPECTS( + debtTotalDeltaDefault == -expectedExposure, + "legacy vault default must reduce DebtTotal by whole-life exposure; delta=" + + to_string(debtTotalDeltaDefault) + " expected=" + to_string(expectedExposure)); + BEAST_EXPECTS( + lossDeltaDefault == -expectedExposure, + "legacy vault default must reverse the earlier impair's LossUnrealized exactly; " + "delta=" + + to_string(lossDeltaDefault) + " expected=" + to_string(expectedExposure)); + + // Confirm the Vault's LEVersion truly never got set, throughout. + { + auto const vaultSle = env.le(broker.vaultKeylet()); + BEAST_EXPECT(vaultSle); + BEAST_EXPECT(!vaultSle->isFieldPresent(sfLEVersion)); + BEAST_EXPECT(getVaultVersion(vaultSle) == VaultVersion::Legacy); + } + } + + // 4. End-to-end trajectory: LoanSet -> 2 LoanPays -> LoanManage(default), + // entirely under the amendment, with independently hand-computed + // expected AssetsTotal/DebtTotal/LossUnrealized/CoverAvailable values at + // each step. 0% interest keeps the arithmetic exact and tractable; the + // divergence from whole-life accounting is already covered directly by + // testCashBasisLoanSetOrigination/LoanPay/LoanManage above, so this test + // focuses purely on an independent, from-scratch trajectory check. + void + testCashBasisEndToEndTrajectory() + { + testcase("cash-basis: end-to-end trajectory"); + + using namespace jtx; + using namespace loan; + using namespace std::chrono_literals; + + PrettyAsset const xrpAsset{xrpIssue(), 1'000'000}; + BrokerParameters const brokerParams{ + .vaultDeposit = 100'000, .managementFeeRate = TenthBips16{0}}; + + Env env(*this, all_ | featureLendingProtocolV1_1); + + Account const lender{"lender"}; + Account const borrower{"borrower"}; + env.fund(XRP(10'000'000), lender, borrower); + env.close(); + + BrokerInfo const broker{createVaultAndBroker(env, xrpAsset, lender, brokerParams)}; + + // Hand computation (all values in XRP, drops == 1e-6 XRP): + // Vault: AssetsTotal starts at 100'000 (the deposit). + // Broker: DebtTotal starts at 0, CoverAvailable starts at 1'000 + // (BrokerParameters::defaults().coverDeposit). + auto const vaultKeylet = broker.vaultKeylet(); + auto const brokerKeylet = broker.brokerKeylet(); + + // All the "human XRP unit" constants below (e.g. `100'000`) are + // converted to raw native (drops) values via xrpAsset(...), since + // that's how the ledger fields are actually denominated. + auto const checkVaultBroker = [&](Number const& assetsTotalUnits, + Number const& debtTotalUnits, + Number const& lossUnrealizedUnits, + Number const& coverAvailableUnits, + char const* step) { + Number const assetsTotal = xrpAsset(assetsTotalUnits).value(); + Number const debtTotal = xrpAsset(debtTotalUnits).value(); + Number const lossUnrealized = xrpAsset(lossUnrealizedUnits).value(); + Number const coverAvailable = xrpAsset(coverAvailableUnits).value(); + + auto const vaultSle = env.le(vaultKeylet); + auto const brokerSle = env.le(brokerKeylet); + BEAST_EXPECT(vaultSle && brokerSle); + BEAST_EXPECTS( + vaultSle->at(sfAssetsTotal) == assetsTotal, + std::string(step) + ": AssetsTotal expected " + to_string(assetsTotal) + " got " + + to_string(Number(vaultSle->at(sfAssetsTotal)))); + BEAST_EXPECTS( + brokerSle->at(sfDebtTotal) == debtTotal, + std::string(step) + ": DebtTotal expected " + to_string(debtTotal) + " got " + + to_string(Number(brokerSle->at(sfDebtTotal)))); + BEAST_EXPECTS( + vaultSle->at(sfLossUnrealized) == lossUnrealized, + std::string(step) + ": LossUnrealized expected " + to_string(lossUnrealized) + + " got " + to_string(Number(vaultSle->at(sfLossUnrealized)))); + BEAST_EXPECTS( + brokerSle->at(sfCoverAvailable) == coverAvailable, + std::string(step) + ": CoverAvailable expected " + to_string(coverAvailable) + + " got " + to_string(Number(brokerSle->at(sfCoverAvailable)))); + }; + + checkVaultBroker(100'000, 0, 0, 1'000, "before LoanSet"); + + // Loan: principal=1200, 0% interest, 12 payments of 100 each, no fees. + Number const principalRequest{1'200}; + std::uint32_t const paymentTotal = 12; + std::uint32_t const paymentInterval = 600; + std::uint32_t const gracePeriod = 60; + + auto const brokerBeforeLoan = env.le(brokerKeylet); + BEAST_EXPECT(brokerBeforeLoan); + auto const loanSequence = brokerBeforeLoan->at(sfLoanSequence); + auto const loanKeylet = keylet::loan(broker.brokerID, loanSequence); + + LoanParameters const loanParams{ + .account = borrower, + .counter = lender, + .principalRequest = principalRequest, + .interest = TenthBips32{0}, + .payTotal = paymentTotal, + .payInterval = paymentInterval, + .gracePd = gracePeriod, + }; + env(loanParams(env, broker)); + env.close(); + + // Origination (cash-basis): AssetsTotal += 0, DebtTotal += principal. + checkVaultBroker(100'000, 1'200, 0, 1'000, "after LoanSet"); + + LoanState const state = getCurrentState(env, broker, loanKeylet); + BEAST_EXPECT(state.periodicPayment == xrpAsset(100).value()); + + // Payment 1: principalPaid=100, interestPaid=0. + // AssetsTotal += 0; DebtTotal -= 100. + env(pay(borrower, loanKeylet.key, xrpAsset(100).value()), Ter(tesSUCCESS)); + env.close(); + checkVaultBroker(100'000, 1'100, 0, 1'000, "after payment 1"); + + // Payment 2: same as above. + env(pay(borrower, loanKeylet.key, xrpAsset(100).value()), Ter(tesSUCCESS)); + env.close(); + checkVaultBroker(100'000, 1'000, 0, 1'000, "after payment 2"); + + // Default (no impair): principalOutstanding remaining is 1'000. + // totalDefaultAmount (cash-basis) = PrincipalOutstanding = 1'000. + // minimumCover = DebtTotal(1'000) * coverRateMin(10%) = 100. + // covered = min(minimumCover * coverRateLiquidation(25%), totalDefaultAmount) + // = min(25, 1'000) = 25. + // defaultCovered = min(covered, CoverAvailable(1'000)) = 25. + // vaultDefaultAmount = 1'000 - 25 = 975. + // DebtTotal -= 1'000 -> 0. CoverAvailable -= 25 -> 975. + // AssetsTotal -= 975 -> 99'025. LossUnrealized unaffected (never impaired). + auto const loanBeforeDefault = env.le(loanKeylet); + BEAST_EXPECT(loanBeforeDefault); + BEAST_EXPECT( + Number(loanBeforeDefault->at(sfPrincipalOutstanding)) == xrpAsset(1'000).value()); + + env.close(state.startDate + std::chrono::seconds((3 * paymentInterval) + gracePeriod) + 1s); + + env(manage(lender, loanKeylet.key, tfLoanDefault), Ter(tesSUCCESS)); + env.close(); + + checkVaultBroker(99'025, 0, 0, 975, "after LoanManage(default)"); + } + void runAmendmentIndependent() { @@ -8570,6 +9553,12 @@ protected: testBugInterestDueDeltaCrash(); testFullLifecycleVaultPnLNearZeroRate(); testLoanSetNearZeroInterestRateSucceeds(); + + testCashBasisLoanSetOrigination(); + testCashBasisLoanPay(); + testCashBasisLoanManage(); + testLegacyVaultKeepsAccrualAfterAmendmentEnabled(); + testCashBasisEndToEndTrajectory(); } // Tests run under each entry in amendmentCombinations(). diff --git a/src/test/app/Vault_test.cpp b/src/test/app/Vault_test.cpp index 12ad7e6782..bd596d6149 100644 --- a/src/test/app/Vault_test.cpp +++ b/src/test/app/Vault_test.cpp @@ -7645,6 +7645,83 @@ class Vault_test : public beast::unit_test::Suite } } + void + testVaultCreateLEVersion() + { + using namespace test::jtx; + + Account const owner{"owner"}; + PrettyAsset const xrpAsset = xrpIssue(); + + { + testcase("VaultCreate LEVersion: featureLendingProtocolV1_1 disabled, field absent"); + Env env{*this}; + env.disableFeature(featureLendingProtocolV1_1); + env.fund(XRP(1'000'000), owner); + env.close(); + + Vault const vault{env}; + auto const [tx, keylet] = vault.create({.owner = owner, .asset = xrpAsset}); + env(tx, Ter(tesSUCCESS)); + env.close(); + + auto const sleVault = env.le(keylet); + BEAST_EXPECT(sleVault); + BEAST_EXPECT(!sleVault->isFieldPresent(sfLEVersion)); + } + + { + testcase( + "VaultCreate LEVersion: featureLendingProtocolV1_1 enabled, LEVersion == " + "VaultVersion::CashBasis"); + Env env{*this}; + env.fund(XRP(1'000'000), owner); + env.close(); + + Vault const vault{env}; + auto const [tx, keylet] = vault.create({.owner = owner, .asset = xrpAsset}); + env(tx, Ter(tesSUCCESS)); + env.close(); + + auto const sleVault = env.le(keylet); + BEAST_EXPECT(sleVault); + BEAST_EXPECT(sleVault->isFieldPresent(sfLEVersion)); + BEAST_EXPECT(sleVault->at(sfLEVersion) == std::to_underlying(VaultVersion::CashBasis)); + } + + { + testcase("VaultCreate rejects LEVersion set in the transaction"); + Env env{*this}; + env.fund(XRP(1'000'000), owner); + env.close(); + + Vault const vault{env}; + auto [tx, keylet] = vault.create({.owner = owner, .asset = xrpAsset}); + tx[sfLEVersion] = 2; + env(tx, Ter(temMALFORMED)); + env.close(); + + BEAST_EXPECT(!env.le(keylet)); + } + + { + testcase("VaultSet rejects LEVersion set in the transaction"); + Env env{*this}; + env.fund(XRP(1'000'000), owner); + env.close(); + + Vault const vault{env}; + auto const [createTx, keylet] = vault.create({.owner = owner, .asset = xrpAsset}); + env(createTx, Ter(tesSUCCESS)); + env.close(); + + auto setTx = vault.set({.owner = owner, .id = keylet.key}); + setTx[sfLEVersion] = 2; + env(setTx, Ter(temMALFORMED)); + env.close(); + } + } + void testVaultDepositFreezeIOU() { @@ -8317,6 +8394,7 @@ public: testVaultEscrowedMPT(); testAssetsMaximum(); testVaultDeleteMemoData(); + testVaultCreateLEVersion(); testBug6LimitBypassWithShares(); testRemoveEmptyHoldingLockedAmount(); testRemoveEmptyHoldingConfidentialBalances(); diff --git a/src/tests/libxrpl/protocol_autogen/ledger_entries/VaultTests.cpp b/src/tests/libxrpl/protocol_autogen/ledger_entries/VaultTests.cpp index 2697924d37..f55d01f606 100644 --- a/src/tests/libxrpl/protocol_autogen/ledger_entries/VaultTests.cpp +++ b/src/tests/libxrpl/protocol_autogen/ledger_entries/VaultTests.cpp @@ -35,6 +35,7 @@ TEST(VaultTests, BuilderSettersRoundTrip) auto const shareMPTIDValue = canonical_UINT192(); auto const withdrawalPolicyValue = canonical_UINT8(); auto const scaleValue = canonical_UINT8(); + auto const lEVersionValue = canonical_UINT8(); VaultBuilder builder{ previousTxnIDValue, @@ -54,6 +55,7 @@ TEST(VaultTests, BuilderSettersRoundTrip) builder.setAssetsMaximum(assetsMaximumValue); builder.setLossUnrealized(lossUnrealizedValue); builder.setScale(scaleValue); + builder.setLEVersion(lEVersionValue); builder.setLedgerIndex(index); builder.setFlags(0x1u); @@ -166,6 +168,14 @@ TEST(VaultTests, BuilderSettersRoundTrip) EXPECT_TRUE(entry.hasScale()); } + { + auto const& expected = lEVersionValue; + auto const actualOpt = entry.getLEVersion(); + ASSERT_TRUE(actualOpt.has_value()); + expectEqualField(expected, *actualOpt, "sfLEVersion"); + EXPECT_TRUE(entry.hasLEVersion()); + } + EXPECT_TRUE(entry.hasLedgerIndex()); auto const ledgerIndex = entry.getLedgerIndex(); ASSERT_TRUE(ledgerIndex.has_value()); @@ -194,6 +204,7 @@ TEST(VaultTests, BuilderFromSleRoundTrip) auto const shareMPTIDValue = canonical_UINT192(); auto const withdrawalPolicyValue = canonical_UINT8(); auto const scaleValue = canonical_UINT8(); + auto const lEVersionValue = canonical_UINT8(); auto sle = std::make_shared(Vault::entryType, index); @@ -212,6 +223,7 @@ TEST(VaultTests, BuilderFromSleRoundTrip) sle->at(sfShareMPTID) = shareMPTIDValue; sle->at(sfWithdrawalPolicy) = withdrawalPolicyValue; sle->at(sfScale) = scaleValue; + sle->at(sfLEVersion) = lEVersionValue; VaultBuilder builderFromSle{sle}; EXPECT_TRUE(builderFromSle.validate()); @@ -390,6 +402,19 @@ TEST(VaultTests, BuilderFromSleRoundTrip) expectEqualField(expected, *fromBuilderOpt, "sfScale"); } + { + auto const& expected = lEVersionValue; + + auto const fromSleOpt = entryFromSle.getLEVersion(); + auto const fromBuilderOpt = entryFromBuilder.getLEVersion(); + + ASSERT_TRUE(fromSleOpt.has_value()); + ASSERT_TRUE(fromBuilderOpt.has_value()); + + expectEqualField(expected, *fromSleOpt, "sfLEVersion"); + expectEqualField(expected, *fromBuilderOpt, "sfLEVersion"); + } + EXPECT_EQ(entryFromSle.getKey(), index); EXPECT_EQ(entryFromBuilder.getKey(), index); } @@ -472,5 +497,7 @@ TEST(VaultTests, OptionalFieldsReturnNullopt) EXPECT_FALSE(entry.getLossUnrealized().has_value()); EXPECT_FALSE(entry.hasScale()); EXPECT_FALSE(entry.getScale().has_value()); + EXPECT_FALSE(entry.hasLEVersion()); + EXPECT_FALSE(entry.getLEVersion().has_value()); } } From 532506541ff521e1b1d336156d828a4ec6d676ed Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Jul 2026 08:17:32 -0400 Subject: [PATCH 03/32] fix: Apply asfDisallowIncomingTrustline blocker to OfferCreate (#6307) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Mayukha Vadari Co-authored-by: Mayukha Vadari --- .../tx/transactors/dex/OfferCreate.cpp | 19 ++- src/test/app/Offer_test.cpp | 160 ++++++++++++++++++ 2 files changed, 175 insertions(+), 4 deletions(-) diff --git a/src/libxrpl/tx/transactors/dex/OfferCreate.cpp b/src/libxrpl/tx/transactors/dex/OfferCreate.cpp index fb47cf0f97..b95d1001e1 100644 --- a/src/libxrpl/tx/transactors/dex/OfferCreate.cpp +++ b/src/libxrpl/tx/transactors/dex/OfferCreate.cpp @@ -283,10 +283,23 @@ OfferCreate::checkAcceptAsset( return asset.visit( [&](Issue const& issue) -> TER { auto const& issuer = issue.getIssuer(); + auto const trustLine = view.read(keylet::trustLine(id, issuer, issue.currency)); + + // Check if the issuer has lsfDisallowIncomingTrustline set. + // If so, the account must already have a trustline to receive tokens. + if (view.rules().enabled(fixCleanup3_4_0) && + issuerAccount->isFlag(lsfDisallowIncomingTrustline)) + { + if (!trustLine) + { + JLOG(j.debug()) << "delay: can't receive IOUs from issuer with " + "DisallowIncomingTrustline set"; + return ((flags & TapRetry) != 0u) ? TER{terNO_LINE} : TER{tecNO_LINE}; + } + } + if (issuerAccount->isFlag(lsfRequireAuth)) { - auto const trustLine = view.read(keylet::trustLine(id, issuer, issue.currency)); - if (!trustLine) { return ((flags & TapRetry) != 0u) ? TER{terNO_LINE} : TER{tecNO_LINE}; @@ -309,8 +322,6 @@ OfferCreate::checkAcceptAsset( } } - auto const trustLine = view.read(keylet::trustLine(id, issue.account, issue.currency)); - if (!trustLine) { return tesSUCCESS; diff --git a/src/test/app/Offer_test.cpp b/src/test/app/Offer_test.cpp index 7fc7161e36..33721e91d8 100644 --- a/src/test/app/Offer_test.cpp +++ b/src/test/app/Offer_test.cpp @@ -4324,6 +4324,165 @@ public: env.require(Balance(bob, gwUSD(10))); } + void + testDisallowIncomingTrustline(FeatureBitset features) + { + testcase("DisallowIncomingTrustline in OfferCreate"); + + // Test that asfDisallowIncomingTrustline flag prevents offer crossing + // when the taker doesn't have a trustline. + // + // 1. alice creates a trustline and sells USD/gw tokens. + // + // 2. gw sets asfDisallowIncomingTrustline flag. + // + // 3. An account without a trustline tries to create an offer for USD/gw. + // Without amendment: succeeds and crosses alice's offer (backward compatible). + // With amendment: fails with tecNO_LINE (new behavior). + // + // 4. An account WITH an existing trustline can create an offer. + // The offer succeeds and crosses alice's offer. + // + // Note: The DisallowIncomingTrustline flag also prevents NEW trustlines + // from being created via TrustSet (enforced by fixDisallowIncomingV1). + // So accounts must create trustlines BEFORE the issuer sets the flag. + + using namespace jtx; + auto const gw = Account("gw"); + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + auto const dan = Account("dan"); + auto const eve = Account("eve"); + auto const gwUSD = gw["USD"]; + + // Test without fixCleanup3_4_0 amendment + { + Env env{*this, features - fixCleanup3_4_0}; + + env.fund(XRP(400000), gw, alice, bob); + env.close(); + + // Alice creates trustline and gets some USD + env(trust(alice, gwUSD(100))); + env.close(); + env(pay(gw, alice, gwUSD(50))); + env.close(); + + // Alice creates sell offer + env(offer(alice, XRP(4000), gwUSD(40))); + env.close(); + env.require(offers(alice, 1)); + + // GW sets DisallowIncomingTrustline flag + env(fset(gw, asfDisallowIncomingTrustline)); + env.close(); + + // Without the amendment, bob can still create offer without trustline + // and the offer should cross (old behavior) + env(offer(bob, gwUSD(40), XRP(4000))); + env.close(); + + // Offer should have crossed + env.require(offers(alice, 0)); + env.require(offers(bob, 0)); + env.require(Balance(bob, gwUSD(40))); + } + + // Test with fixCleanup3_4_0 amendment + { + Env env{*this, features}; + + env.fund(XRP(400000), gw, alice, bob, carol, dan); + env.close(); + + // Alice creates trustline and gets some USD + env(trust(alice, gwUSD(100))); + env.close(); + env(pay(gw, alice, gwUSD(50))); + env.close(); + + // Bob and carol create trustlines BEFORE the flag is set + env(trust(bob, gwUSD(100))); + env.close(); + env(trust(carol, gwUSD(100))); + env.close(); + + // Alice creates sell offer + env(offer(alice, XRP(4000), gwUSD(40))); + env.close(); + env.require(offers(alice, 1)); + env.require(Balance(alice, gwUSD(50))); + + // GW sets DisallowIncomingTrustline flag + env(fset(gw, asfDisallowIncomingTrustline)); + env.close(); + + // Dan tries to create offer without trustline - should fail + env(offer(dan, gwUSD(40), XRP(4000)), Ter(tecNO_LINE)); + env.close(); + + // Alice's offer should still exist + env.require(offers(alice, 1)); + env.require(Balance(alice, gwUSD(50))); + + // Dan shouldn't have any offers or balance + env.require(offers(dan, 0)); + BEAST_EXPECT(env.le(keylet::trustLine(dan, gwUSD)) == nullptr); + + // Bob already has trustline, so his offer should succeed and cross + env(offer(bob, gwUSD(40), XRP(4000))); + env.close(); + + // Offer should have crossed + env.require(offers(alice, 0)); + env.require(offers(bob, 0)); + env.require(Balance(alice, gwUSD(10))); + env.require(Balance(bob, gwUSD(40))); + + // Test scenario where carol already has a trustline (created before flag was set) + // Carol should be able to create offer since trustline already exists + env(pay(gw, alice, gwUSD(50))); + env.close(); + env(offer(alice, XRP(1000), gwUSD(10))); + env.close(); + env.require(offers(alice, 1)); + + env(offer(carol, gwUSD(10), XRP(1000))); + env.close(); + + // Offer should have crossed + env.require(offers(alice, 0)); + env.require(offers(carol, 0)); + env.require(Balance(alice, gwUSD(50))); + env.require(Balance(carol, gwUSD(10))); + + // Test that gw can clear the flag + env(fclear(gw, asfDisallowIncomingTrustline)); + env.close(); + + // Create new account eve without trustline + env.fund(XRP(400000), eve); + env.close(); + + // Bob creates another sell offer + env(pay(gw, bob, gwUSD(50))); + env.close(); + env(offer(bob, XRP(5000), gwUSD(50))); + env.close(); + env.require(offers(bob, 1)); + + // Eve should now be able to create offer without trustline (flag is cleared) + env(offer(eve, gwUSD(50), XRP(5000))); + env.close(); + + // Offer should have crossed + env.require(offers(bob, 0)); + env.require(offers(eve, 0)); + env.require(Balance(eve, gwUSD(50))); + } + } + void testRCSmoketest(FeatureBitset features) { @@ -5167,6 +5326,7 @@ public: testSelfPayUnlimitedFunds(features); testRequireAuth(features); testMissingAuth(features); + testDisallowIncomingTrustline(features); testRCSmoketest(features); testSelfAuth(features); testDeletedOfferIssuer(features); From 3c0659d26b6b9a3342d79b7034ae3677e4b3bc3b Mon Sep 17 00:00:00 2001 From: Olek <115580134+oleks-rip@users.noreply.github.com> Date: Thu, 30 Jul 2026 08:18:33 -0400 Subject: [PATCH 04/32] test: Add confidential mpt bulletproof tests (#7816) --- sanitizers/suppressions/ubsan.supp | 1 + src/test/app/ConfidentialTransfer_test.cpp | 425 +++++++++++++++++++++ src/test/jtx/ConfidentialTransfer.h | 30 ++ 3 files changed, 456 insertions(+) diff --git a/sanitizers/suppressions/ubsan.supp b/sanitizers/suppressions/ubsan.supp index 7e3e02f855..a67a4a0ca3 100644 --- a/sanitizers/suppressions/ubsan.supp +++ b/sanitizers/suppressions/ubsan.supp @@ -192,6 +192,7 @@ unsigned-integer-overflow:rpc/handlers/orderbook/GetAggregatePrice.cpp # Test-only intentional overflow/underflow in fixture and unit-test arithmetic. unsigned-integer-overflow:tests/libxrpl/basics/RangeSet.cpp unsigned-integer-overflow:test/app/Batch_test.cpp +unsigned-integer-overflow:test/app/ConfidentialTransfer_test.cpp unsigned-integer-overflow:test/app/Invariants_test.cpp unsigned-integer-overflow:test/app/Loan_test.cpp unsigned-integer-overflow:test/app/NFToken_test.cpp diff --git a/src/test/app/ConfidentialTransfer_test.cpp b/src/test/app/ConfidentialTransfer_test.cpp index d3e0182db5..0fc5d6f845 100644 --- a/src/test/app/ConfidentialTransfer_test.cpp +++ b/src/test/app/ConfidentialTransfer_test.cpp @@ -5469,6 +5469,429 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase } } + void + testSendOverdraftBulletproof(FeatureBitset features) + { + uint64_t const balance = 100; + testSendOverdraftBulletproofImpl(features, balance, balance); // SUCCEED + testSendOverdraftBulletproofImpl(features, balance, balance + 1); // FAIL + } + + void + testSendOverdraftBulletproofImpl(FeatureBitset features, unsigned balance, unsigned amt) + { + testcase("Send: overdraft prevention via bulletproof"); + using namespace test::jtx; + + // Attack scenario: Alice has 100 tokens, tries to send 101 to Bob. + // The client-side check in mpt-crypto:mpt_utility.cpp:743 prevents honest + // clients from creating this proof. We bypass it by manually + // constructing a forged proof to demonstrate that the ledger's + // range proof verification catches the overdraft. + + Env env{*this, features}; + Account const alice("alice"), bob("bob"), issuer("issuer"); + + uint64_t const aliceBalance = balance; + uint64_t const aliceAmount = amt; + uint64_t const aliceRemaining = aliceBalance - aliceAmount; + + // Setup: Alice has 100 tokens converted to confidential + ConfidentialEnv confEnv{ + env, + issuer, + {{.account = alice, .payAmount = 1000, .convertAmount = aliceBalance}, + {.account = bob, .payAmount = 1000, .convertAmount = 30}}}; + auto& mptIssuer = confEnv.mpt; + + std::pair errors = aliceAmount > aliceBalance + ? std::make_pair(-1, TER(tecBAD_PROOF)) + : std::make_pair(0, TER(tesSUCCESS)); + + unsigned const numParticipants = 3; + + // Verify Alice's actual balance before attack + { + auto const balance = requireOptional( + mptIssuer.getDecryptedBalance(alice, MPTTester::holderEncryptedSpending), + "Missing Alice's balance"); + BEAST_EXPECT(balance == aliceBalance); + } + + // We cannot use ConfidentialSendSetup directly because it would + // call mpt_get_confidential_send_proof which has a client-side + // check (amount > balance) at line 743 in mpt_utility.cpp. + // Instead, we manually construct the transaction components. + + Buffer const randomElgamal = generateBlindingFactor(); + Buffer const randomBalance = generateBlindingFactor(); + + // Create encrypted amounts (using the OVERDRAFT amount) + Buffer const aliceEncAmt = mptIssuer.encryptAmount(alice, aliceAmount, randomElgamal); + Buffer const bobEncAmt = mptIssuer.encryptAmount(bob, aliceAmount, randomElgamal); + Buffer const issuerEncAmt = mptIssuer.encryptAmount(issuer, aliceAmount, randomElgamal); + + // Create commitments + // IMPORTANT: Amount commitment uses same randomness as ElGamal encryption! + Buffer const amtCommit = mptIssuer.getPedersenCommitment(aliceAmount, randomElgamal); + Buffer const balanceCommit = mptIssuer.getPedersenCommitment(aliceBalance, randomBalance); + + // Get Alice's current encrypted spending balance + Buffer const aliceEncBalance = requireOptional( + mptIssuer.getEncryptedBalance(alice, MPTTester::holderEncryptedSpending), + "Missing Alice's encrypted spending balance"); + + uint32_t const version = mptIssuer.getMPTokenVersion(alice); + auto const ctxHash = getSendContextHash( + alice.id(), mptIssuer.issuanceID(), env.seq(alice), bob.id(), version); + + // Now we need to manually generate the sigma proof part. + // The sigma proof verifies ciphertext consistency and commitments, + // but doesn't check the range. We'll construct it with the overdraft + // amount to bypass the client-side check. + + // Generate the sigma proof manually using the lower-level secp256k1 API + auto* ctx = mpt_secp256k1_context(); + Buffer sigmaProof(SECP256K1_COMPACT_STANDARD_PROOF_SIZE); + + // Parse all public keys and ciphertexts + secp256k1_pubkey c1, c2Alice, c2Bob, c2Issuer; + // Parse sender's ciphertext C1 (first 33(kCompressedEcPointLength) bytes) + auto x = secp256k1_ec_pubkey_parse(ctx, &c1, aliceEncAmt.data(), kCompressedEcPointLength); + if (!BEAST_EXPECTS(x == 1, "Failed to parse C1")) + return; + // Parse C2 components for all recipients + x = secp256k1_ec_pubkey_parse( + ctx, &c2Alice, aliceEncAmt.data() + kCompressedEcPointLength, kCompressedEcPointLength); + auto y = secp256k1_ec_pubkey_parse( + ctx, &c2Bob, bobEncAmt.data() + kCompressedEcPointLength, kCompressedEcPointLength); + auto z = secp256k1_ec_pubkey_parse( + ctx, + &c2Issuer, + issuerEncAmt.data() + kCompressedEcPointLength, + kCompressedEcPointLength); + if (!BEAST_EXPECTS(x == 1 && y == 1 && z == 1, "Failed to parse C2 components")) + return; + secp256k1_pubkey c2Vec[] = {c2Alice, c2Bob, c2Issuer}; + + // Parse public keys + secp256k1_pubkey pkAlice, pkBob, pkIssuer; + auto alicePubKey = requireOptional(mptIssuer.getPubKey(alice), "Missing alice pubkey"); + auto bobPubKey = requireOptional(mptIssuer.getPubKey(bob), "Missing bob pubkey"); + auto issuerPubKey = requireOptional(mptIssuer.getPubKey(issuer), "Missing issuer pubkey"); + x = secp256k1_ec_pubkey_parse(ctx, &pkAlice, alicePubKey.data(), kCompressedEcPointLength); + y = secp256k1_ec_pubkey_parse(ctx, &pkBob, bobPubKey.data(), kCompressedEcPointLength); + z = secp256k1_ec_pubkey_parse( + ctx, &pkIssuer, issuerPubKey.data(), kCompressedEcPointLength); + if (!BEAST_EXPECTS(x == 1 && y == 1 && z == 1, "Failed to parse public keys")) + return; + secp256k1_pubkey pkVec[] = {pkAlice, pkBob, pkIssuer}; + + // Parse commitments + secp256k1_pubkey pcAmount, pcBalance, b1, b2; + x = secp256k1_ec_pubkey_parse(ctx, &pcAmount, amtCommit.data(), kCompressedEcPointLength); + y = secp256k1_ec_pubkey_parse( + ctx, &pcBalance, balanceCommit.data(), kCompressedEcPointLength); + if (!BEAST_EXPECTS(x == 1 && y == 1, "Failed to parse commitments")) + return; + // Parse balance ciphertext + x = secp256k1_ec_pubkey_parse(ctx, &b1, aliceEncBalance.data(), kCompressedEcPointLength); + y = secp256k1_ec_pubkey_parse( + ctx, &b2, aliceEncBalance.data() + kCompressedEcPointLength, kCompressedEcPointLength); + if (!BEAST_EXPECTS(x == 1 && y == 1, "Failed to parse balance ciphertext")) + return; + + // Get Alice's private key + auto alicePrivKey = requireOptional(mptIssuer.getPrivKey(alice), "Missing alice privkey"); + + // Generate the compact sigma proof (part of mpt_get_confidential_send_proof) + // This will succeed because sigma proof doesn't check amount vs balance + x = secp256k1_compact_standard_prove( + ctx, + sigmaProof.data(), + aliceAmount, + aliceBalance, + randomElgamal.data(), + alicePrivKey.data(), + randomBalance.data(), + numParticipants, + &c1, + c2Vec, + pkVec, + &pcAmount, + &pkAlice, + &pcBalance, + &b1, + &b2, + ctxHash.data()); + if (!BEAST_EXPECTS(x == 1, "Failed to generate sigma proof")) + return; + + // Direct verification + x = secp256k1_compact_standard_verify( + ctx, + sigmaProof.data(), + numParticipants, + &c1, + c2Vec, + pkVec, + &pcAmount, + &pkAlice, + &pcBalance, + &b1, + &b2, + ctxHash.data()); + if (!BEAST_EXPECTS(x == 1, "Sigma verification failed")) + return; + + // Compute the remaining blinding factor: r_remaining = r_balance - r_amount + // This is required because the ledger homomorphically computes: + // C_remaining = C_balance - C_amount = Commit(remaining, r_balance - r_amount) + Buffer randomRemaining(kEcBlindingFactorLength); + Buffer negRandomElgamal(kEcBlindingFactorLength); + secp256k1_mpt_scalar_negate(negRandomElgamal.data(), randomElgamal.data()); + secp256k1_mpt_scalar_add( + randomRemaining.data(), randomBalance.data(), negRandomElgamal.data()); + + // Now forge the bulletproof claiming + auto const forgedBulletproof = getForgedBulletproof( + {aliceAmount, aliceRemaining}, {randomElgamal, randomRemaining}, ctxHash); + + // Combine sigma proof + forged bulletproof + Buffer combinedProof(SECP256K1_COMPACT_STANDARD_PROOF_SIZE + kEcDoubleBulletproofLength); + std::memcpy(combinedProof.data(), sigmaProof.data(), SECP256K1_COMPACT_STANDARD_PROOF_SIZE); + std::memcpy( + combinedProof.data() + SECP256K1_COMPACT_STANDARD_PROOF_SIZE, + forgedBulletproof.data(), + kEcDoubleBulletproofLength); + + // Direct verification + x = mpt_verify_send_range_proof( + combinedProof.data() + SECP256K1_COMPACT_STANDARD_PROOF_SIZE, + amtCommit.data(), + balanceCommit.data(), + ctxHash.data()); + if (!BEAST_EXPECTS(x == errors.first, "Forged proof passed validation")) + return; + + // Attempt the transaction with forged proof + // Expected to FAIL with tecBAD_PROOF + mptIssuer.send({ + .account = alice, + .dest = bob, + .amt = aliceAmount, + .proof = strHex(combinedProof), + .senderEncryptedAmt = aliceEncAmt, + .destEncryptedAmt = bobEncAmt, + .issuerEncryptedAmt = issuerEncAmt, + .amountCommitment = amtCommit, + .balanceCommitment = balanceCommit, + .err = errors.second, + }); + + // Verify Alice's balance unchanged (attack prevented!) + { + auto const balance = requireOptional( + mptIssuer.getDecryptedBalance(alice, MPTTester::holderEncryptedSpending), + "Missing post-attack balance"); + if (aliceAmount > aliceBalance) + { + BEAST_EXPECT(balance == aliceBalance); + } + else + { + BEAST_EXPECT(balance < aliceBalance); + } + } + } + + void + testConvertBackOverdraftBulletproof(FeatureBitset features) + { + uint64_t const balance = 100; + testConvertBackOverdraftBulletproofImpl(features, balance, balance); // SUCCEED + testConvertBackOverdraftBulletproofImpl(features, balance, balance + 1); // FAIL + } + + void + testConvertBackOverdraftBulletproofImpl(FeatureBitset features, uint64_t balance, uint64_t amt) + { + testcase("Convert back: overdraft prevention via bulletproof"); + using namespace test::jtx; + + // Attack scenario: Bob has 100 confidential tokens, tries to convert back 101. + // The client-side check in mpt_get_convert_back_proof would prevent honest + // clients from creating this proof. We bypass it by manually constructing + // a forged proof to demonstrate that the ledger's bulletproof verification + // catches the overdraft. + + Env env{*this, features}; + Account const alice("alice"), bob("bob"), carol("carol"); + + uint64_t const bobBalance = balance; + uint64_t const convertAmount = amt; + uint64_t const bobRemaining = bobBalance - convertAmount; + + // Setup: Bob and Carol both have confidential balance + // Carol ensures outstanding amount >= convertAmount (bypass preclaim check) + // This allows us to test the bulletproof specifically + ConfidentialEnv confEnv{ + env, + alice, + { + {.account = bob, .payAmount = 1000, .convertAmount = bobBalance}, + {.account = carol, + .payAmount = 1000, + .convertAmount = std::max(convertAmount, bobBalance + 1)}, + }}; + auto& mptAlice = confEnv.mpt; + + std::pair errors = convertAmount > bobBalance + ? std::make_pair(-1, TER(tecBAD_PROOF)) + : std::make_pair(0, TER(tesSUCCESS)); + + // Verify Bob's actual balance before attack + { + auto const balance = requireOptional( + mptAlice.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending), + "Missing Bob's balance"); + BEAST_EXPECT(balance == bobBalance); + } + + // We cannot use the standard getConvertBackProof because it calls + // mpt_get_convert_back_proof which has client-side validation. + // Instead, we manually construct the sigma proof and forge the bulletproof. + + Buffer const blindingFactor = generateBlindingFactor(); + Buffer const pcBlindingFactor = generateBlindingFactor(); + + // Create encrypted amounts for the conversion + Buffer const bobEncAmt = mptAlice.encryptAmount(bob, convertAmount, blindingFactor); + Buffer const issuerEncAmt = mptAlice.encryptAmount(alice, convertAmount, blindingFactor); + + // Create Pedersen commitment to the current balance + Buffer const balanceCommit = mptAlice.getPedersenCommitment(bobBalance, pcBlindingFactor); + + // Get Bob's current encrypted spending balance + Buffer const bobEncBalance = requireOptional( + mptAlice.getEncryptedBalance(bob, MPTTester::holderEncryptedSpending), + "Missing Bob's encrypted spending balance"); + + uint32_t const version = mptAlice.getMPTokenVersion(bob); + auto const ctxHash = + getConvertBackContextHash(bob.id(), mptAlice.issuanceID(), env.seq(bob), version); + + // Now manually generate the compact sigma proof for ConvertBack + auto* ctx = mpt_secp256k1_context(); + Buffer sigmaProof(SECP256K1_COMPACT_CONVERTBACK_PROOF_SIZE); + + // Parse the holder's public key + secp256k1_pubkey pkBob; + auto bobPubKey = requireOptional(mptAlice.getPubKey(bob), "Missing bob pubkey"); + auto x = secp256k1_ec_pubkey_parse(ctx, &pkBob, bobPubKey.data(), kCompressedEcPointLength); + if (!BEAST_EXPECTS(x == 1, "Failed to parse Bob's public key")) + return; + + // Parse balance commitment + secp256k1_pubkey pcBalance; + x = secp256k1_ec_pubkey_parse( + ctx, &pcBalance, balanceCommit.data(), kCompressedEcPointLength); + if (!BEAST_EXPECTS(x == 1, "Failed to parse balance commitment")) + return; + + // Parse balance ciphertext (B1, B2) + secp256k1_pubkey b1, b2; + x = secp256k1_ec_pubkey_parse(ctx, &b1, bobEncBalance.data(), kCompressedEcPointLength); + auto y = secp256k1_ec_pubkey_parse( + ctx, &b2, bobEncBalance.data() + kCompressedEcPointLength, kCompressedEcPointLength); + if (!BEAST_EXPECTS(x == 1 && y == 1, "Failed to parse balance ciphertext")) + return; + + // Get Bob's private key + auto bobPrivKey = requireOptional(mptAlice.getPrivKey(bob), "Missing bob privkey"); + + // Generate the compact sigma proof for ConvertBack + // This verifies balance ownership and commitment linkage + x = secp256k1_compact_convertback_prove( + ctx, + sigmaProof.data(), + bobBalance, + bobPrivKey.data(), + pcBlindingFactor.data(), + &pkBob, + &b1, + &b2, + &pcBalance, + ctxHash.data()); + if (!BEAST_EXPECTS(x == 1, "Failed to generate convertback sigma proof")) + return; + + // Verify the sigma proof passes (it doesn't check range) + x = secp256k1_compact_convertback_verify( + ctx, sigmaProof.data(), &pkBob, &b1, &b2, &pcBalance, ctxHash.data()); + if (!BEAST_EXPECTS(x == 1, "Sigma verification failed")) + return; + + // Now forge the single bulletproof claiming the remaining balance is valid + // For ConvertBack, we need to prove: (balance - convertAmount) >= 0 + // We create a commitment to the remainder and generate a bulletproof for it + + // The bulletproof needs the blinding factor for the remainder commitment + // The ledger computes: C_remainder = C_balance - convertAmount*G + // So the blinding factor is just pcBlindingFactor (no randomness in convertAmount*G) + + auto const forgedBulletproof = + getForgedSingleBulletproof(bobRemaining, pcBlindingFactor, ctxHash); + + // Combine sigma proof + forged bulletproof + Buffer combinedProof(kEcConvertBackProofLength); + std::memcpy( + combinedProof.data(), sigmaProof.data(), SECP256K1_COMPACT_CONVERTBACK_PROOF_SIZE); + std::memcpy( + combinedProof.data() + SECP256K1_COMPACT_CONVERTBACK_PROOF_SIZE, + forgedBulletproof.data(), + kEcSingleBulletproofLength); + + // Direct verification of the full proof + x = mpt_verify_convert_back_proof( + combinedProof.data(), + bobPubKey.data(), + bobEncBalance.data(), + balanceCommit.data(), + convertAmount, + ctxHash.data()); + if (!BEAST_EXPECTS(x == errors.first, "Forged proof verification mismatch")) + return; + + // Attempt the transaction with forged proof + // Expected to FAIL with tecBAD_PROOF when convertAmount > bobBalance + mptAlice.convertBack({ + .account = bob, + .amt = convertAmount, + .proof = combinedProof, + .holderEncryptedAmt = bobEncAmt, + .issuerEncryptedAmt = issuerEncAmt, + .blindingFactor = blindingFactor, + .pedersenCommitment = balanceCommit, + .err = errors.second, + }); + + // Verify Bob's balance unchanged (attack prevented!) + { + auto const postBalance = requireOptional( + mptAlice.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending), + "Missing post-attack balance"); + if (convertAmount > bobBalance) + { + BEAST_EXPECT(postBalance == bobBalance); + } + else + { + BEAST_EXPECT(postBalance < bobBalance); + } + } + } + void testConvertBackBulletproof(FeatureBitset features) { @@ -8143,6 +8566,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase testConvertBackWithAuditor(features); testConvertBackPedersenProof(features); testConvertBackBulletproof(features); + testConvertBackOverdraftBulletproof(features); // Homomorphic operation tests testSendHomomorphicOverflow(features); @@ -8177,6 +8601,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase testSendInvalidProofContextBinding(features); testSendForgedEqualityProof(features); testSendForgedRangeProof(features); + testSendOverdraftBulletproof(features); testSendNegativeValueMalleability(features); testSendFiatShamirBinding(features); testSendProofComponentReuse(features); diff --git a/src/test/jtx/ConfidentialTransfer.h b/src/test/jtx/ConfidentialTransfer.h index 404ddbe31d..465bac03db 100644 --- a/src/test/jtx/ConfidentialTransfer.h +++ b/src/test/jtx/ConfidentialTransfer.h @@ -94,6 +94,36 @@ protected: return proof; } + // Generate a forged single bulletproof for a single value and blinding factor. + // Used to test ConvertBack overdraft prevention via bulletproof verification. + static Buffer + getForgedSingleBulletproof( + uint64_t value, + Buffer const& blindingFactor, + uint256 const& contextHash) + { + auto* const ctx = mpt_secp256k1_context(); + + secp256k1_pubkey h; + secp256k1_mpt_get_h_generator(ctx, &h); + + Buffer proof(kEcSingleBulletproofLength); + size_t proofLen = kEcSingleBulletproofLength; + + if (secp256k1_bulletproof_prove_agg( + ctx, + proof.data(), + &proofLen, + &value, + blindingFactor.data(), + 1, // m = 1 (single bulletproof) + &h, + contextHash.data()) == 0) + Throw("Failed to generate forged single bulletproof"); + + return proof; + } + // Get a bad ciphertext with valid structure but cryptographic invalid for // testing purposes. For preflight test purposes. static Buffer const& From 21cd6154076a2a9f58471b76f47d14c3c2416a38 Mon Sep 17 00:00:00 2001 From: Bart Date: Thu, 30 Jul 2026 11:02:05 -0400 Subject: [PATCH 05/32] perf: Replace node ID by depth in `TMLedgerNode` (#6353) Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com> --- include/xrpl/basics/Slice.h | 8 + include/xrpl/proto/xrpl.proto | 10 +- include/xrpl/shamap/SHAMap.h | 59 +++- include/xrpl/shamap/SHAMapLeafNode.h | 15 + src/libxrpl/shamap/SHAMapNodeID.cpp | 3 +- src/libxrpl/shamap/SHAMapSync.cpp | 109 ++++---- src/test/app/LedgerNodeHelpers_test.cpp | 260 ++++++++++++++++++ src/test/overlay/ProtocolVersion_test.cpp | 4 +- src/tests/libxrpl/shamap/SHAMapSync.cpp | 18 +- src/xrpld/app/ledger/InboundLedger.h | 14 +- src/xrpld/app/ledger/LedgerNodeHelpers.h | 52 ++++ src/xrpld/app/ledger/detail/InboundLedger.cpp | 125 ++++++--- .../app/ledger/detail/InboundLedgers.cpp | 18 +- .../app/ledger/detail/InboundTransactions.cpp | 38 ++- .../app/ledger/detail/LedgerNodeHelpers.cpp | 89 ++++++ .../app/ledger/detail/TransactionAcquire.cpp | 18 +- .../app/ledger/detail/TransactionAcquire.h | 6 +- src/xrpld/overlay/Peer.h | 1 + src/xrpld/overlay/detail/PeerImp.cpp | 239 +++++++++++++--- src/xrpld/overlay/detail/PeerImp.h | 5 +- src/xrpld/overlay/detail/ProtocolVersion.cpp | 1 + 21 files changed, 898 insertions(+), 194 deletions(-) create mode 100644 src/test/app/LedgerNodeHelpers_test.cpp create mode 100644 src/xrpld/app/ledger/LedgerNodeHelpers.h create mode 100644 src/xrpld/app/ledger/detail/LedgerNodeHelpers.cpp diff --git a/include/xrpl/basics/Slice.h b/include/xrpl/basics/Slice.h index 36e7615c3a..75c9b8c7bd 100644 --- a/include/xrpl/basics/Slice.h +++ b/include/xrpl/basics/Slice.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -251,4 +252,11 @@ makeSlice(std::basic_string const& s) return Slice(s.data(), s.size()); } +template +Slice +makeSlice(std::basic_string_view s) +{ + return Slice(s.data(), s.size()); +} + } // namespace xrpl diff --git a/include/xrpl/proto/xrpl.proto b/include/xrpl/proto/xrpl.proto index d49920201e..bef5ec1d76 100644 --- a/include/xrpl/proto/xrpl.proto +++ b/include/xrpl/proto/xrpl.proto @@ -246,7 +246,15 @@ message TMGetObjectByHash { message TMLedgerNode { required bytes nodedata = 1; - optional bytes nodeid = 2; // missing for ledger base data + + // Used when protocol version <2.3. Not set for ledger base data. + optional bytes nodeid = 2; + + // Used when protocol version >=2.3. Neither value is set for ledger base data. + oneof reference { + bytes id = 3; // Set for inner nodes. + uint32 depth = 4; // Set for leaf nodes. + } } enum TMLedgerInfoType { diff --git a/include/xrpl/shamap/SHAMap.h b/include/xrpl/shamap/SHAMap.h index a1194ccfd3..e198c472fa 100644 --- a/include/xrpl/shamap/SHAMap.h +++ b/include/xrpl/shamap/SHAMap.h @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -95,6 +94,21 @@ enum class SHAMapState { * * See https://en.wikipedia.org/wiki/Merkle_tree */ + +/** + * Holds a SHAMap node's identity, leaf status, and serialized data. Used by + * getNodeFat to return node data for peer synchronization. + */ +struct SHAMapNodeData +{ + SHAMapNodeID nodeID; + // The `data` field (a Blob, 8-byte aligned) needs 4 bytes of padding after the `nodeID` field + // (36 bytes, 4-byte aligned) regardless of what comes between them, so `isLeaf` costs nothing + // extra here. Moving it after `data` would add 8 bytes to the size of this struct instead. + bool isLeaf; + Blob data; +}; + class SHAMap { private: @@ -289,10 +303,10 @@ public: std::vector> getMissingNodes(int maxNodes, SHAMapSyncFilter const* filter); - bool + [[nodiscard]] bool getNodeFat( SHAMapNodeID const& wanted, - std::vector>& data, + std::vector& data, bool fatLeaves, std::uint32_t depth) const; @@ -321,10 +335,45 @@ public: void serializeRoot(Serializer& s) const; + /** + * Add a root node to the SHAMap during synchronization. + * + * This function is used when receiving the root node of a SHAMap from a peer during ledger + * synchronization. The node must already have been deserialized. + * + * @param hash The expected hash of the root node. + * @param rootNode A deserialized root node to add. + * @param filter Optional sync filter to track received nodes. + * @return Status indicating whether the node was useful, duplicate, or invalid. + * + * @note This function expects the rootNode to be a valid, deserialized SHAMapTreeNode. The + * caller is responsible for deserialization and basic validation before calling this + * function. + */ SHAMapAddNode - addRootNode(SHAMapHash const& hash, Slice const& rootNode, SHAMapSyncFilter const* filter); + addRootNode(SHAMapHash const& hash, SHAMapTreeNodePtr rootNode, SHAMapSyncFilter const* filter); + + /** + * Add a known node at a specific position in the SHAMap during synchronization. + * + * This function is used when receiving nodes from peers during ledger synchronization. The node + * is inserted at the position specified by nodeID. The node must already have been + * deserialized. + * + * @param nodeID The position in the tree where this node belongs. + * @param treeNode A deserialized tree node to add. + * @param filter Optional sync filter to track received nodes. + * @return Status indicating whether the node was useful, duplicate, or invalid. + * + * @note This function expects the treeNode to be a valid, deserialized SHAMapTreeNode. The + * caller is responsible for deserialization and basic validation before calling this + * function. This also means that the nodeID must be consistent with the node's content. + */ SHAMapAddNode - addKnownNode(SHAMapNodeID const& nodeID, Slice const& rawNode, SHAMapSyncFilter const* filter); + addKnownNode( + SHAMapNodeID const& nodeID, + SHAMapTreeNodePtr treeNode, + SHAMapSyncFilter const* filter); // status functions void diff --git a/include/xrpl/shamap/SHAMapLeafNode.h b/include/xrpl/shamap/SHAMapLeafNode.h index 26cfde9fe8..ab5bd574ed 100644 --- a/include/xrpl/shamap/SHAMapLeafNode.h +++ b/include/xrpl/shamap/SHAMapLeafNode.h @@ -1,6 +1,9 @@ #pragma once #include +#include +#include +#include #include #include #include @@ -60,4 +63,16 @@ public: getString(SHAMapNodeID const&) const final; }; +/** + * Return the key of the item held by a SHAMap leaf node. + * + * @param node a node known to be a leaf (see SHAMapTreeNode::isLeaf). + */ +inline uint256 const& +leafKey(SHAMapTreeNode const& node) +{ + XRPL_ASSERT(node.isLeaf(), "xrpl::leafKey : node is a leaf"); + return safeDowncast(node).peekItem()->key(); +} + } // namespace xrpl diff --git a/src/libxrpl/shamap/SHAMapNodeID.cpp b/src/libxrpl/shamap/SHAMapNodeID.cpp index 16aaafe709..a511fc038c 100644 --- a/src/libxrpl/shamap/SHAMapNodeID.cpp +++ b/src/libxrpl/shamap/SHAMapNodeID.cpp @@ -129,7 +129,8 @@ selectBranch(SHAMapNodeID const& id, uint256 const& hash) SHAMapNodeID SHAMapNodeID::createID(int depth, uint256 const& key) { - XRPL_ASSERT((depth >= 0) && (depth < 65), "xrpl::SHAMapNodeID::createID : valid branch input"); + XRPL_ASSERT( + depth >= 0 && depth <= SHAMap::kLeafDepth, "xrpl::SHAMapNodeID::createID : valid depth"); return SHAMapNodeID(depth, key & depthMask(depth)); } diff --git a/src/libxrpl/shamap/SHAMapSync.cpp b/src/libxrpl/shamap/SHAMapSync.cpp index cc30426f9d..cbed6885c9 100644 --- a/src/libxrpl/shamap/SHAMapSync.cpp +++ b/src/libxrpl/shamap/SHAMapSync.cpp @@ -107,7 +107,7 @@ SHAMap::visitNodes(std::function const& function) const void SHAMap::visitDifferences( - SHAMap const* have, + SHAMap const* map, std::function const& function) const { // Visit every node in this SHAMap that is not present @@ -118,13 +118,13 @@ SHAMap::visitDifferences( if (root_->getHash().isZero()) return; - if ((have != nullptr) && (root_->getHash() == have->root_->getHash())) + if ((map != nullptr) && (root_->getHash() == map->root_->getHash())) return; if (root_->isLeaf()) { auto leaf = intr_ptr::staticPointerCast(root_); - if ((have == nullptr) || !have->hasLeafNode(leaf->peekItem()->key(), leaf->getHash())) + if ((map == nullptr) || !map->hasLeafNode(leaf->peekItem()->key(), leaf->getHash())) function(*root_); return; } @@ -149,18 +149,15 @@ SHAMap::visitDifferences( if (!node->isEmptyBranch(i)) { auto const& childHash = node->getChildHash(i); - SHAMapNodeID const childID = nodeID.getChildNodeID(i); + auto const childID = nodeID.getChildNodeID(i); auto next = descendThrow(node, i); if (next->isInner()) { - if ((have == nullptr) || !have->hasInnerNode(childID, childHash)) + if ((map == nullptr) || !map->hasInnerNode(childID, childHash)) stack.emplace(safeDowncast(next), childID); } - else if ( - (have == nullptr) || - !have->hasLeafNode( - safeDowncast(next)->peekItem()->key(), childHash)) + else if ((map == nullptr) || !map->hasLeafNode(leafKey(*next), childHash)) { if (!function(*next)) return; @@ -414,7 +411,7 @@ SHAMap::getMissingNodes(int max, SHAMapSyncFilter const* filter) bool SHAMap::getNodeFat( SHAMapNodeID const& wanted, - std::vector>& data, + std::vector& data, bool fatLeaves, std::uint32_t depth) const { @@ -460,7 +457,7 @@ SHAMap::getNodeFat( // Add this node to the reply s.erase(); node->serializeForWire(s); - data.emplace_back(nodeID, s.getData()); + data.emplace_back(nodeID, node->isLeaf(), s.getData()); if (node->isInner()) { @@ -490,7 +487,7 @@ SHAMap::getNodeFat( // Just include this node s.erase(); childNode->serializeForWire(s); - data.emplace_back(childID, s.getData()); + data.emplace_back(childID, childNode->isLeaf(), s.getData()); } } } @@ -508,25 +505,33 @@ SHAMap::serializeRoot(Serializer& s) const } SHAMapAddNode -SHAMap::addRootNode(SHAMapHash const& hash, Slice const& rootNode, SHAMapSyncFilter const* filter) +SHAMap::addRootNode( + SHAMapHash const& hash, + SHAMapTreeNodePtr rootNode, + SHAMapSyncFilter const* filter) { + XRPL_ASSERT(cowid_ >= 1, "xrpl::SHAMap::addRootNode : valid cowid"); + XRPL_ASSERT(rootNode, "xrpl::SHAMap::addRootNode : non-null root node"); + // we already have a root_ node if (root_->getHash().isNonZero()) { - JLOG(journal_.trace()) << "got root node, already have one"; - XRPL_ASSERT(root_->getHash() == hash, "xrpl::SHAMap::addRootNode : valid hash input"); + JLOG(journal_.trace()) << "Got root node, already have one"; + XRPL_ASSERT(root_->getHash() == hash, "xrpl::SHAMap::addRootNode : valid hash"); return SHAMapAddNode::duplicate(); } - XRPL_ASSERT(cowid_ >= 1, "xrpl::SHAMap::addRootNode : valid cowid"); - auto node = SHAMapTreeNode::makeFromWire(rootNode); - if (!node || node->getHash() != hash) + if (rootNode->getHash() != hash) + { + JLOG(journal_.warn()) << "Corrupt root node received: expected hash " << hash << ", got " + << rootNode->getHash(); return SHAMapAddNode::invalid(); + } if (backed_) - canonicalize(hash, node); + canonicalize(hash, rootNode); - root_ = node; + root_ = std::move(rootNode); if (root_->isLeaf()) clearSynching(); @@ -543,9 +548,18 @@ SHAMap::addRootNode(SHAMapHash const& hash, Slice const& rootNode, SHAMapSyncFil } SHAMapAddNode -SHAMap::addKnownNode(SHAMapNodeID const& node, Slice const& rawNode, SHAMapSyncFilter const* filter) +SHAMap::addKnownNode( + SHAMapNodeID const& nodeID, + SHAMapTreeNodePtr treeNode, + SHAMapSyncFilter const* filter) { - XRPL_ASSERT(!node.isRoot(), "xrpl::SHAMap::addKnownNode : valid node input"); + XRPL_ASSERT(!nodeID.isRoot(), "xrpl::SHAMap::addKnownNode : valid node"); + XRPL_ASSERT(treeNode, "xrpl::SHAMap::addKnownNode : non-null tree node"); + XRPL_ASSERT( + !treeNode->isLeaf() || + SHAMapNodeID::createID(nodeID.getDepth(), leafKey(*treeNode)).getNodeID() == + nodeID.getNodeID(), + "xrpl::SHAMap::addKnownNode : leaf position consistent with node ID"); if (!isSynching()) { @@ -559,14 +573,15 @@ SHAMap::addKnownNode(SHAMapNodeID const& node, Slice const& rawNode, SHAMapSyncF while (currNode->isInner() && !safeDowncast(currNode)->isFullBelow(generation) && - (currNodeID.getDepth() < node.getDepth())) + (currNodeID.getDepth() < nodeID.getDepth())) { - int const branch = selectBranch(currNodeID, node.getNodeID()); + int const branch = selectBranch(currNodeID, nodeID.getNodeID()); XRPL_ASSERT(branch >= 0, "xrpl::SHAMap::addKnownNode : valid branch"); auto inner = safeDowncast(currNode); if (inner->isEmptyBranch(branch)) { - JLOG(journal_.warn()) << "Add known node for empty branch" << node; + JLOG(journal_.warn()) << "Add known node " << nodeID << " for empty branch " << branch + << " at " << currNodeID; return SHAMapAddNode::invalid(); } @@ -582,67 +597,45 @@ SHAMap::addKnownNode(SHAMapNodeID const& node, Slice const& rawNode, SHAMapSyncF if (currNode != nullptr) continue; - auto newNode = SHAMapTreeNode::makeFromWire(rawNode); - - if (!newNode || childHash != newNode->getHash()) + if (childHash != treeNode->getHash()) { - JLOG(journal_.warn()) << "Corrupt node received"; + JLOG(journal_.warn()) << "Corrupt node " << nodeID << " received: expected hash " + << childHash << ", got " << treeNode->getHash(); return SHAMapAddNode::invalid(); } - // In rare cases, a node can still be corrupt even after hash - // validation. For leaf nodes, we perform an additional check to - // ensure the node's position in the tree is consistent with its - // content to prevent inconsistencies that could - // propagate further down the line. - if (newNode->isLeaf()) - { - auto const& actualKey = - safeDowncast(newNode.get())->peekItem()->key(); - - // Validate that this leaf belongs at the target position - auto const expectedNodeID = SHAMapNodeID::createID(node.getDepth(), actualKey); - if (expectedNodeID.getNodeID() != node.getNodeID()) - { - JLOG(journal_.debug()) - << "Leaf node position mismatch: " - << "expected=" << expectedNodeID.getNodeID() << ", actual=" << node.getNodeID(); - return SHAMapAddNode::invalid(); - } - } - // Inner nodes must be at a level strictly less than 64 // but leaf nodes (while notionally at level 64) can be // at any depth up to and including 64: if ((currNodeID.getDepth() > kLeafDepth) || - (newNode->isInner() && currNodeID.getDepth() == kLeafDepth)) + (treeNode->isInner() && currNodeID.getDepth() == kLeafDepth)) { // Map is provably invalid state_ = SHAMapState::Invalid; return SHAMapAddNode::useful(); } - if (currNodeID != node) + if (currNodeID != nodeID) { // Either this node is broken or we didn't request it (yet) - JLOG(journal_.warn()) << "unable to hook node " << node; + JLOG(journal_.warn()) << "unable to hook node " << nodeID; JLOG(journal_.info()) << " stuck at " << currNodeID; - JLOG(journal_.info()) << "got depth=" << node.getDepth() + JLOG(journal_.info()) << "got depth=" << nodeID.getDepth() << ", walked to= " << currNodeID.getDepth(); return SHAMapAddNode::useful(); } if (backed_) - canonicalize(childHash, newNode); + canonicalize(childHash, treeNode); - newNode = prevNode->canonicalizeChild(branch, std::move(newNode)); + treeNode = prevNode->canonicalizeChild(branch, std::move(treeNode)); if (filter != nullptr) { Serializer s; - newNode->serializeWithPrefix(s); + treeNode->serializeWithPrefix(s); filter->gotNode( - false, childHash, ledgerSeq_, std::move(s.modData()), newNode->getType()); + false, childHash, ledgerSeq_, std::move(s.modData()), treeNode->getType()); } return SHAMapAddNode::useful(); diff --git a/src/test/app/LedgerNodeHelpers_test.cpp b/src/test/app/LedgerNodeHelpers_test.cpp new file mode 100644 index 0000000000..a9e4e3ebfc --- /dev/null +++ b/src/test/app/LedgerNodeHelpers_test.cpp @@ -0,0 +1,260 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include + +namespace xrpl::tests { + +class LedgerNodeHelpers_test : public beast::unit_test::Suite +{ + static boost::intrusive_ptr + makeTestItem(std::uint32_t seed) + { + Serializer s; + s.add32(seed); + s.add32(seed + 1); + s.add32(seed + 2); + return makeShamapitem(s.getSHA512Half(), s.slice()); + } + + static std::string + serializeNode(SHAMapTreeNodePtr const& node) + { + Serializer s; + node->serializeForWire(s); + auto const slice = s.slice(); + return std::string(slice.begin(), slice.end()); + } + + void + testGetTreeNode() + { + testcase("getTreeNode"); + + // Valid: inner node. It must have at least one child for `serializeNode` to work. + { + auto const innerNode = intr_ptr::makeShared(1); + auto const childNode = intr_ptr::makeShared(1); + innerNode->setChild(0, childNode); + auto const innerData = serializeNode(innerNode); + auto const result = getTreeNode(innerData); + BEAST_EXPECT(result && result->isInner()); + } + + // Valid: leaf node. + { + auto const leafItem = makeTestItem(12345); + auto const leafNode = intr_ptr::makeShared(leafItem, 1); + auto const leafData = serializeNode(leafNode); + auto const result = getTreeNode(leafData); + BEAST_EXPECT(result && result->isLeaf()); + } + + // Invalid: empty data. + { + auto const result = getTreeNode(""); + BEAST_EXPECT(!result); + } + + // Invalid: garbage data. + { + auto const result = getTreeNode("invalid"); + BEAST_EXPECT(!result); + } + + // Invalid: truncated data. + { + auto const leafItem = makeTestItem(54321); + auto const leafNode = intr_ptr::makeShared(leafItem, 1); + // Truncate the data to trigger an exception in SHAMapTreeNode::makeAccountState when + // the data is used to deserialize the node. + uint256 const tag; + auto const leafData = serializeNode(leafNode).substr(0, tag.kBytes - 1); + auto const result = getTreeNode(leafData); + BEAST_EXPECT(!result); + } + } + + void + testGetSHAMapNodeID() + { + testcase("getSHAMapNodeID"); + + { + // Tests using inner nodes at various depths. + auto const innerNode = intr_ptr::makeShared(1); + auto const childNode = intr_ptr::makeShared(1); + innerNode->setChild(0, childNode); + auto const innerData = serializeNode(innerNode); + + // Valid: legacy `nodeid` field at arbitrary depth. + { + auto const innerDepth = 3; + auto const innerID = SHAMapNodeID::createID(innerDepth, uint256{}); + + protocol::TMLedgerNode ledgerNode; + ledgerNode.set_nodedata(innerData); + ledgerNode.set_nodeid(innerID.getRawString()); + auto const result = getSHAMapNodeID(ledgerNode, *innerNode); + BEAST_EXPECT(result == innerID); + } + + // Valid: new `id` field at minimum depth. + { + auto const innerDepth = 0; + auto const innerID = SHAMapNodeID::createID(innerDepth, uint256{}); + + protocol::TMLedgerNode ledgerNode; + ledgerNode.set_nodedata(innerData); + ledgerNode.set_id(innerID.getRawString()); + auto const result = getSHAMapNodeID(ledgerNode, *innerNode); + BEAST_EXPECT(result == innerID); + } + + // Invalid: new `depth` field should not be used for inner nodes. + { + protocol::TMLedgerNode ledgerNode; + ledgerNode.set_nodedata(innerData); + ledgerNode.set_depth(10); + auto const result = getSHAMapNodeID(ledgerNode, *innerNode); + BEAST_EXPECT(!result); + } + + // Invalid: both legacy `nodeid` and new `id` fields set for an inner node. + { + auto const innerDepth = 9; + auto const innerID = SHAMapNodeID::createID(innerDepth, uint256{}); + + protocol::TMLedgerNode ledgerNode; + ledgerNode.set_nodedata(innerData); + ledgerNode.set_nodeid(innerID.getRawString()); + ledgerNode.set_id(innerID.getRawString()); + auto const result = getSHAMapNodeID(ledgerNode, *innerNode); + BEAST_EXPECT(!result); + } + } + + { + // Tests using leaf nodes at various depths. + auto const leafItem = makeTestItem(12345); + auto const leafNode = intr_ptr::makeShared(leafItem, 1); + auto const leafData = serializeNode(leafNode); + auto const leafKey = leafItem->key(); + + // Valid: legacy `nodeid` field at arbitrary depth. + { + auto const kLeafDepth = 5; + auto const leafID = SHAMapNodeID::createID(kLeafDepth, leafKey); + + protocol::TMLedgerNode ledgerNode; + ledgerNode.set_nodedata(leafData); + ledgerNode.set_nodeid(leafID.getRawString()); + auto const result = getSHAMapNodeID(ledgerNode, *leafNode); + BEAST_EXPECT(result == leafID); + } + + // Invalid: new `id` field should not be used for leaf nodes. + { + auto const kLeafDepth = 5; + auto const leafID = SHAMapNodeID::createID(kLeafDepth, leafKey); + + protocol::TMLedgerNode ledgerNode; + ledgerNode.set_nodedata(leafData); + ledgerNode.set_id(leafID.getRawString()); + auto const result = getSHAMapNodeID(ledgerNode, *leafNode); + BEAST_EXPECT(!result); + } + + // Valid: new `depth` field at minimum depth. + { + auto const kLeafDepth = 0; + auto const leafID = SHAMapNodeID::createID(kLeafDepth, leafKey); + + protocol::TMLedgerNode ledgerNode; + ledgerNode.set_nodedata(leafData); + ledgerNode.set_depth(kLeafDepth); + auto const result = getSHAMapNodeID(ledgerNode, *leafNode); + BEAST_EXPECT(result == leafID); + } + + // Valid: new `depth` field at arbitrary depth between minimum and maximum. + { + auto const kLeafDepth = 10; + auto const leafID = SHAMapNodeID::createID(kLeafDepth, leafKey); + + protocol::TMLedgerNode ledgerNode; + ledgerNode.set_nodedata(leafData); + ledgerNode.set_depth(kLeafDepth); + auto const result = getSHAMapNodeID(ledgerNode, *leafNode); + BEAST_EXPECT(result == leafID); + } + + // Valid: new `depth` field at maximum depth. + // Note that we do not test a depth greater than the maximum depth, because the proto + // message is assumed to have been validated by the time the getSHAMapNodeID function is + // called. + { + auto const kLeafDepth = SHAMap::kLeafDepth; + auto const leafID = SHAMapNodeID::createID(kLeafDepth, leafKey); + + protocol::TMLedgerNode ledgerNode; + ledgerNode.set_nodedata(leafData); + ledgerNode.set_depth(kLeafDepth); + auto const result = getSHAMapNodeID(ledgerNode, *leafNode); + BEAST_EXPECT(result == leafID); + } + + // Invalid: legacy `nodeid` field where the node ID is inconsistent with the key. + { + auto const otherItem = makeTestItem(54321); + auto const otherNode = + intr_ptr::makeShared(otherItem, 1); + auto const otherData = serializeNode(otherNode); + auto const otherKey = otherItem->key(); + auto const otherDepth = 1; + auto const otherID = SHAMapNodeID::createID(otherDepth, otherKey); + + protocol::TMLedgerNode ledgerNode; + ledgerNode.set_nodedata(otherData); + ledgerNode.set_nodeid(otherID.getRawString()); + auto const result = getSHAMapNodeID(ledgerNode, *leafNode); + BEAST_EXPECT(!result); + } + } + + // Invalid: no field set. + { + auto const innerNode = intr_ptr::makeShared(1); + protocol::TMLedgerNode ledgerNode; + ledgerNode.set_nodedata("test_data"); + auto const result = getSHAMapNodeID(ledgerNode, *innerNode); + BEAST_EXPECT(!result); + } + } + +public: + void + run() override + { + testGetTreeNode(); + testGetSHAMapNodeID(); + } +}; + +BEAST_DEFINE_TESTSUITE(LedgerNodeHelpers, app, xrpl); + +} // namespace xrpl::tests diff --git a/src/test/overlay/ProtocolVersion_test.cpp b/src/test/overlay/ProtocolVersion_test.cpp index 2fc8e4447d..e31a574502 100644 --- a/src/test/overlay/ProtocolVersion_test.cpp +++ b/src/test/overlay/ProtocolVersion_test.cpp @@ -63,8 +63,8 @@ public: negotiateProtocolVersion("RTXP/1.2, XRPL/2.0, XRPL/2.1") == makeProtocol(2, 1)); BEAST_EXPECT(negotiateProtocolVersion("XRPL/2.2") == makeProtocol(2, 2)); BEAST_EXPECT( - negotiateProtocolVersion("RTXP/1.2, XRPL/2.2, XRPL/2.3, XRPL/999.999") == - makeProtocol(2, 2)); + negotiateProtocolVersion("RTXP/1.2, XRPL/2.3, XRPL/2.4, XRPL/999.999") == + makeProtocol(2, 3)); BEAST_EXPECT(negotiateProtocolVersion("XRPL/999.999, WebSocket/1.0") == std::nullopt); BEAST_EXPECT(negotiateProtocolVersion("") == std::nullopt); } diff --git a/src/tests/libxrpl/shamap/SHAMapSync.cpp b/src/tests/libxrpl/shamap/SHAMapSync.cpp index 5cefbae8a1..400509d217 100644 --- a/src/tests/libxrpl/shamap/SHAMapSync.cpp +++ b/src/tests/libxrpl/shamap/SHAMapSync.cpp @@ -1,4 +1,3 @@ -#include #include #include #include @@ -112,14 +111,16 @@ TEST_F(SHAMapSyncTest, sync) destination.setSynching(); { - std::vector> a; + std::vector a; ASSERT_TRUE(source.getNodeFat(SHAMapNodeID(), a, randBool(eng_), randInt(eng_, 2))); ASSERT_FALSE(a.empty()) << "NodeSize"; - ASSERT_TRUE( - destination.addRootNode(source.getHash(), makeSlice(a[0].second), nullptr).isGood()); + auto node = SHAMapTreeNode::makeFromWire(makeSlice(a[0].data)); + if (!node) + FAIL() << "Could not create node"; + ASSERT_TRUE(destination.addRootNode(source.getHash(), std::move(node), nullptr).isGood()); } do @@ -133,7 +134,7 @@ TEST_F(SHAMapSyncTest, sync) break; // get as many nodes as possible based on this information - std::vector> b; + std::vector b; for (auto& it : nodesMissing) { @@ -155,7 +156,12 @@ TEST_F(SHAMapSyncTest, sync) // Keep failures fatal here because this loop is data-dependent. // non-deterministic number of times and the number of tests run // should be deterministic - if (!destination.addKnownNode(i.first, makeSlice(i.second), nullptr).isUseful()) + auto node = SHAMapTreeNode::makeFromWire(makeSlice(i.data)); + if (!node) + FAIL() << "Could not create node"; + if (i.isLeaf != node->isLeaf()) + FAIL() << "Node is not a leaf"; + if (!destination.addKnownNode(i.nodeID, std::move(node), nullptr).isUseful()) FAIL() << "Known node was not useful"; } } while (true); diff --git a/src/xrpld/app/ledger/InboundLedger.h b/src/xrpld/app/ledger/InboundLedger.h index 31ca4169ce..9a7ee510f6 100644 --- a/src/xrpld/app/ledger/InboundLedger.h +++ b/src/xrpld/app/ledger/InboundLedger.h @@ -6,7 +6,6 @@ #include #include -#include #include #include #include @@ -24,7 +23,7 @@ #include #include #include -#include +#include #include #include @@ -154,16 +153,19 @@ private: processData(std::shared_ptr peer, protocol::TMLedgerData const& data); bool - takeHeader(std::string const& data); + takeHeader(std::string_view data); void - receiveNode(protocol::TMLedgerData const& packet, SHAMapAddNode&); + receiveNode( + std::shared_ptr const& peer, + protocol::TMLedgerData const& packet, + SHAMapAddNode& san); bool - takeTxRootNode(Slice const& data, SHAMapAddNode&); + takeTxRootNode(std::string_view data, SHAMapAddNode& san); bool - takeAsRootNode(Slice const& data, SHAMapAddNode&); + takeAsRootNode(std::string_view data, SHAMapAddNode& san); std::vector neededTxHashes(int max, SHAMapSyncFilter const* filter) const; diff --git a/src/xrpld/app/ledger/LedgerNodeHelpers.h b/src/xrpld/app/ledger/LedgerNodeHelpers.h new file mode 100644 index 0000000000..9df9ab06c7 --- /dev/null +++ b/src/xrpld/app/ledger/LedgerNodeHelpers.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include + +#include +#include + +namespace protocol { +class TMLedgerNode; +} // namespace protocol + +namespace xrpl { + +/** + * @brief Deserializes a SHAMapTreeNode from wire format data. + * + * This function attempts to create a SHAMapTreeNode from the provided data string. If the data is + * malformed or deserialization fails, the function returns a nullptr instead of throwing an + * exception. + * + * @param data The serialized node data in wire format. + * @return The deserialized tree node if successful, or a nullptr if deserialization fails. + */ +[[nodiscard]] SHAMapTreeNodePtr +getTreeNode(std::string_view data); + +/** + * @brief Extracts or reconstructs the SHAMapNodeID from a ledger node proto message. + * + * This function retrieves the SHAMapNodeID for a tree node, with behavior that depends on which + * field is set and the node type (inner vs. leaf). + * + * When the legacy `nodeid` field is set in the message: + * - For all nodes: Deserializes the node ID from the field. + * - For leaf nodes: Validates that the node ID is consistent with the leaf's key. + * + * When the new `id` or `depth` field is set in the message: + * - For inner nodes: Deserializes the node ID from the `id` field. + * - For leaf nodes: Reconstructs the node ID using both the depth from the `depth` field and the + * key from the leaf node's item. + * Note that root nodes may be inner nodes or leaf nodes. + * + * @param ledgerNode The validated protocol message containing the ledger node data. + * @param treeNode The deserialized tree node (inner or leaf node). + * @return An optional containing the node ID if extraction/reconstruction succeeds, or std::nullopt + * if the required fields are missing or validation fails. + */ +[[nodiscard]] std::optional +getSHAMapNodeID(protocol::TMLedgerNode const& ledgerNode, SHAMapTreeNode const& treeNode); + +} // namespace xrpl diff --git a/src/xrpld/app/ledger/detail/InboundLedger.cpp b/src/xrpld/app/ledger/detail/InboundLedger.cpp index 55a2a9d283..b3dafcf5e6 100644 --- a/src/xrpld/app/ledger/detail/InboundLedger.cpp +++ b/src/xrpld/app/ledger/detail/InboundLedger.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -44,8 +45,8 @@ #include #include #include -#include #include +#include #include #include #include @@ -779,7 +780,7 @@ InboundLedger::filterNodes( */ // data must not have hash prefix bool -InboundLedger::takeHeader(std::string const& data) +InboundLedger::takeHeader(std::string_view data) { // Return value: true=normal, false=bad data JLOG(journal_.trace()) << "got header acquiring ledger " << hash_; @@ -825,7 +826,10 @@ InboundLedger::takeHeader(std::string const& data) * Call with a lock */ void -InboundLedger::receiveNode(protocol::TMLedgerData const& packet, SHAMapAddNode& san) +InboundLedger::receiveNode( + std::shared_ptr const& peer, + protocol::TMLedgerData const& packet, + SHAMapAddNode& san) { if (!haveHeader_) { @@ -868,32 +872,47 @@ InboundLedger::receiveNode(protocol::TMLedgerData const& packet, SHAMapAddNode& { auto const f = filter.get(); - for (auto const& node : packet.nodes()) + for (auto const& ledgerNode : packet.nodes()) { - auto const nodeID = deserializeSHAMapNodeID(node.nodeid()); + auto treeNode = getTreeNode(ledgerNode.nodedata()); + if (!treeNode) + { + JLOG(journal_.warn()) + << "Got invalid node data for ledger " << hash_ << " from peer " << peer->id(); + peer->charge(Resource::kFeeInvalidData, "ledger_node.node_data invalid"); + san.incInvalid(); + return; + } + auto const nodeID = getSHAMapNodeID(ledgerNode, *treeNode); if (!nodeID) - throw std::runtime_error("data does not properly deserialize"); - - if (nodeID->isRoot()) { - san += map.addRootNode(rootHash, makeSlice(node.nodedata()), f); - } - else - { - san += map.addKnownNode(*nodeID, makeSlice(node.nodedata()), f); + JLOG(journal_.warn()) + << "Got invalid node id for ledger " << hash_ << " from peer " << peer->id(); + peer->charge(Resource::kFeeInvalidData, "ledger_node.node_id invalid"); + san.incInvalid(); + return; } - if (!san.isGood()) + auto const result = nodeID->isRoot() + ? map.addRootNode(rootHash, std::move(treeNode), f) + : map.addKnownNode(*nodeID, std::move(treeNode), f); + san += result; + + if (result.isInvalid()) { - JLOG(journal_.warn()) << "Received bad node data"; + JLOG(journal_.warn()) << "Got invalid node " << *nodeID << " for ledger " << hash_ + << " from peer " << peer->id(); + peer->charge(Resource::kFeeInvalidData, "ledger_node invalid"); return; } } } catch (std::exception const& e) { - JLOG(journal_.error()) << "Received bad node data: " << e.what(); + // If we get here it is not necessarily because the node was bad, so don't charge the peer. + JLOG(journal_.error()) << "Could not process node for ledger " << hash_ << " from peer " + << peer->id() << ": " << e.what(); san.incInvalid(); return; } @@ -922,7 +941,7 @@ InboundLedger::receiveNode(protocol::TMLedgerData const& packet, SHAMapAddNode& * Call with a lock */ bool -InboundLedger::takeAsRootNode(Slice const& data, SHAMapAddNode& san) +InboundLedger::takeAsRootNode(std::string_view data, SHAMapAddNode& san) { if (failed_ || haveState_) { @@ -938,10 +957,19 @@ InboundLedger::takeAsRootNode(Slice const& data, SHAMapAddNode& san) // LCOV_EXCL_STOP } + auto treeNode = getTreeNode(data); + if (!treeNode) + { + JLOG(journal_.warn()) << "Got invalid AS root node data for ledger " << hash_; + san.incInvalid(); + return false; + } + AccountStateSF filter(ledger_->stateMap().family().db(), app_.getLedgerMaster()); - san += - ledger_->stateMap().addRootNode(SHAMapHash{ledger_->header().accountHash}, data, &filter); - return san.isGood(); + auto const result = ledger_->stateMap().addRootNode( + SHAMapHash{ledger_->header().accountHash}, std::move(treeNode), &filter); + san += result; + return !result.isInvalid(); } /** @@ -949,7 +977,7 @@ InboundLedger::takeAsRootNode(Slice const& data, SHAMapAddNode& san) * Call with a lock */ bool -InboundLedger::takeTxRootNode(Slice const& data, SHAMapAddNode& san) +InboundLedger::takeTxRootNode(std::string_view data, SHAMapAddNode& san) { if (failed_ || haveTransactions_) { @@ -965,9 +993,19 @@ InboundLedger::takeTxRootNode(Slice const& data, SHAMapAddNode& san) // LCOV_EXCL_STOP } + auto treeNode = getTreeNode(data); + if (!treeNode) + { + JLOG(journal_.warn()) << "Got invalid TX root node data for ledger " << hash_; + san.incInvalid(); + return false; + } + TransactionStateSF filter(ledger_->txMap().family().db(), app_.getLedgerMaster()); - san += ledger_->txMap().addRootNode(SHAMapHash{ledger_->header().txHash}, data, &filter); - return san.isGood(); + auto const result = ledger_->txMap().addRootNode( + SHAMapHash{ledger_->header().txHash}, std::move(treeNode), &filter); + san += result; + return !result.isInvalid(); } std::vector @@ -1065,20 +1103,33 @@ InboundLedger::processData(std::shared_ptr peer, protocol::TMLedgerData co } if (!haveState_ && (packet.nodes().size() > 1) && - !takeAsRootNode(makeSlice(packet.nodes(1).nodedata()), san)) + !takeAsRootNode(packet.nodes(1).nodedata(), san)) { - JLOG(journal_.warn()) << "Included AS root invalid"; + JLOG(journal_.warn()) << "Included AS root invalid for ledger " << hash_ + << " from peer " << peer->id(); + if (san.isInvalid()) + { + peer->charge(Resource::kFeeInvalidData, "ledger_data invalid AS root"); + return -1; + } } if (!haveTransactions_ && (packet.nodes().size() > 2) && - !takeTxRootNode(makeSlice(packet.nodes(2).nodedata()), san)) + !takeTxRootNode(packet.nodes(2).nodedata(), san)) { - JLOG(journal_.warn()) << "Included TX root invalid"; + JLOG(journal_.warn()) << "Included TX root invalid for ledger " << hash_ + << " from peer " << peer->id(); + if (san.isInvalid()) + { + peer->charge(Resource::kFeeInvalidData, "ledger_data invalid TX root"); + return -1; + } } } catch (std::exception const& ex) { - JLOG(journal_.warn()) << "Included AS/TX root invalid: " << ex.what(); + JLOG(journal_.warn()) << "Included AS/TX root invalid for ledger " << hash_ + << " from peer " << peer->id() << ": " << ex.what(); using namespace std::string_literals; peer->charge(Resource::kFeeInvalidData, "ledger_data "s + ex.what()); return -1; @@ -1102,24 +1153,18 @@ InboundLedger::processData(std::shared_ptr peer, protocol::TMLedgerData co ScopedLockType const sl(mtx_); - // Verify node IDs and data are complete - for (auto const& node : packet.nodes()) - { - if (!node.has_nodeid() || !node.has_nodedata()) - { - JLOG(journal_.warn()) << "Got bad node"; - peer->charge(Resource::kFeeMalformedRequest, "ledger_data bad node"); - return -1; - } - } - SHAMapAddNode san; - receiveNode(packet, san); + receiveNode(peer, packet, san); JLOG(journal_.debug()) << "Ledger " << ((packet.type() == protocol::liTX_NODE) ? "TX" : "AS") << " node stats: " << san.get(); + // `san` accumulates across the whole packet, so `isInvalid()` (bad_ > 0) does not mean the + // packet had no useful nodes: credit whatever good/useful nodes were sent rather than + // discarding everything because one node in an otherwise-good packet was bad. + // Note: Peer charges for invalid/malformed data are issued from within receiveNode at the + // exact failure site, so the peer is only charged for problems they are responsible for. if (san.isUseful()) progress_ = true; diff --git a/src/xrpld/app/ledger/detail/InboundLedgers.cpp b/src/xrpld/app/ledger/detail/InboundLedgers.cpp index dc361694cf..4d565ca674 100644 --- a/src/xrpld/app/ledger/detail/InboundLedgers.cpp +++ b/src/xrpld/app/ledger/detail/InboundLedgers.cpp @@ -2,13 +2,13 @@ #include #include +#include #include #include #include #include #include -#include #include #include #include @@ -252,23 +252,17 @@ public: Serializer s; try { - for (int i = 0; i < packetPtr->nodes().size(); ++i) + for (auto const& ledgerNode : packetPtr->nodes()) { - auto const& node = packetPtr->nodes(i); - - if (!node.has_nodeid() || !node.has_nodedata()) - return; - - auto newNode = SHAMapTreeNode::makeFromWire(makeSlice(node.nodedata())); - - if (!newNode) + auto const treeNode = getTreeNode(ledgerNode.nodedata()); + if (!treeNode) return; s.erase(); - newNode->serializeWithPrefix(s); + treeNode->serializeWithPrefix(s); app_.getLedgerMaster().addFetchPack( - newNode->getHash().asUInt256(), std::make_shared(s.begin(), s.end())); + treeNode->getHash().asUInt256(), std::make_shared(s.begin(), s.end())); } } catch (std::exception const&) // NOLINT(bugprone-empty-catch) diff --git a/src/xrpld/app/ledger/detail/InboundTransactions.cpp b/src/xrpld/app/ledger/detail/InboundTransactions.cpp index 9b50a1584f..d735a97d28 100644 --- a/src/xrpld/app/ledger/detail/InboundTransactions.cpp +++ b/src/xrpld/app/ledger/detail/InboundTransactions.cpp @@ -1,11 +1,11 @@ #include +#include #include #include #include #include -#include #include #include #include @@ -14,6 +14,7 @@ #include #include #include +#include #include @@ -137,34 +138,45 @@ public: if (ta == nullptr) { - peer->charge(Resource::kFeeUselessData, "ledger_data"); + peer->charge(Resource::kFeeUselessData, "ledger_data useless"); return; } - std::vector> data; + std::vector> data; data.reserve(packet.nodes().size()); - for (auto const& node : packet.nodes()) + for (auto const& ledgerNode : packet.nodes()) { - if (!node.has_nodeid() || !node.has_nodedata()) + auto treeNode = getTreeNode(ledgerNode.nodedata()); + if (!treeNode) { - peer->charge(Resource::kFeeMalformedRequest, "ledger_data"); + JLOG(j_.warn()) << "Got invalid node data for TX set " << hash << " from peer " + << peer->id(); + peer->charge(Resource::kFeeInvalidData, "ledger_node.node_data invalid"); return; } - auto const id = deserializeSHAMapNodeID(node.nodeid()); - - if (!id) + auto const nodeID = getSHAMapNodeID(ledgerNode, *treeNode); + if (!nodeID) { - peer->charge(Resource::kFeeInvalidData, "ledger_data"); + JLOG(j_.warn()) << "Got invalid node id for TX set " << hash << " from peer " + << peer->id(); + peer->charge(Resource::kFeeInvalidData, "ledger_node.node_id invalid"); return; } - data.emplace_back(*id, makeSlice(node.nodedata())); + data.emplace_back(*nodeID, std::move(treeNode)); } - if (!ta->takeNodes(data, peer).isUseful()) - peer->charge(Resource::kFeeUselessData, "ledger_data not useful"); + auto const san = ta->takeNodes(std::move(data), peer); + if (san.isInvalid()) + { + peer->charge(Resource::kFeeInvalidData, "ledger_data invalid"); + } + else if (!san.isUseful()) + { + peer->charge(Resource::kFeeUselessData, "ledger_data useless"); + } } void diff --git a/src/xrpld/app/ledger/detail/LedgerNodeHelpers.cpp b/src/xrpld/app/ledger/detail/LedgerNodeHelpers.cpp new file mode 100644 index 0000000000..531dba59f9 --- /dev/null +++ b/src/xrpld/app/ledger/detail/LedgerNodeHelpers.cpp @@ -0,0 +1,89 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +namespace xrpl { + +SHAMapTreeNodePtr +getTreeNode(std::string_view data) +{ + auto const slice = makeSlice(data); + try + { + return SHAMapTreeNode::makeFromWire(slice); + } + catch (std::exception const&) + { + return {}; + } +} + +std::optional +getSHAMapNodeID(protocol::TMLedgerNode const& ledgerNode, SHAMapTreeNode const& treeNode) +{ + if (ledgerNode.has_id() || ledgerNode.has_depth()) + { + // Reject ambiguous messages that mix the legacy and new reference fields. + if (ledgerNode.has_nodeid()) + return std::nullopt; + + if (treeNode.isInner()) + { + if (!ledgerNode.has_id()) + return std::nullopt; + + REACHABLE("xrpl::getSHAMapNodeID : inner node ID from id field"); + return deserializeSHAMapNodeID(ledgerNode.id()); + } + + if (treeNode.isLeaf()) + { + SOMETIMES( + ledgerNode.has_depth() && ledgerNode.depth() > SHAMap::kLeafDepth, + "xrpl::getSHAMapNodeID : leaf depth exceeds max"); + if (!ledgerNode.has_depth() || ledgerNode.depth() > SHAMap::kLeafDepth) + return std::nullopt; + + auto const key = leafKey(treeNode); + REACHABLE("xrpl::getSHAMapNodeID : leaf node ID reconstructed from depth"); + return SHAMapNodeID::createID(ledgerNode.depth(), key); + } + // LCOV_EXCL_START + UNREACHABLE("xrpl::getSHAMapNodeID : tree node is neither inner nor leaf"); + return std::nullopt; + // LCOV_EXCL_STOP + } + + if (!ledgerNode.has_nodeid()) + return std::nullopt; + + auto nodeID = deserializeSHAMapNodeID(ledgerNode.nodeid()); + if (!nodeID.has_value()) + return std::nullopt; + + if (treeNode.isLeaf()) + { + auto const key = leafKey(treeNode); + auto const expectedID = SHAMapNodeID::createID(static_cast(nodeID->getDepth()), key); + SOMETIMES( + nodeID->getNodeID() != expectedID.getNodeID(), + "xrpl::getSHAMapNodeID : legacy leaf ID inconsistent with key"); + if (nodeID->getNodeID() != expectedID.getNodeID()) + return std::nullopt; + } + + return nodeID; +} + +} // namespace xrpl diff --git a/src/xrpld/app/ledger/detail/TransactionAcquire.cpp b/src/xrpld/app/ledger/detail/TransactionAcquire.cpp index 62312b04d2..db99299fd6 100644 --- a/src/xrpld/app/ledger/detail/TransactionAcquire.cpp +++ b/src/xrpld/app/ledger/detail/TransactionAcquire.cpp @@ -7,13 +7,13 @@ #include #include -#include #include #include #include #include #include #include +#include #include @@ -171,7 +171,7 @@ TransactionAcquire::trigger(std::shared_ptr const& peer) SHAMapAddNode TransactionAcquire::takeNodes( - std::vector> const& data, + std::vector> data, std::shared_ptr const& peer) { ScopedLockType const sl(mtx_); @@ -195,7 +195,7 @@ TransactionAcquire::takeNodes( ConsensusTransSetSF sf(app_, app_.getTempNodeCache()); - for (auto const& d : data) + for (auto& d : data) { if (d.first.isRoot()) { @@ -203,18 +203,22 @@ TransactionAcquire::takeNodes( { JLOG(journal_.debug()) << "Got root TXS node, already have it"; } - else if (!map_->addRootNode(SHAMapHash{hash_}, d.second, nullptr).isGood()) + else if (!map_->addRootNode(SHAMapHash{hash_}, std::move(d.second), nullptr) + .isGood()) { - JLOG(journal_.warn()) << "TX acquire got bad root node"; + JLOG(journal_.warn()) << "TX acquire got bad root node for TX set " << hash_ + << " from peer " << peer->id(); + return SHAMapAddNode::invalid(); } else { haveRoot_ = true; } } - else if (!map_->addKnownNode(d.first, d.second, &sf).isGood()) + else if (!map_->addKnownNode(d.first, std::move(d.second), &sf).isGood()) { - JLOG(journal_.warn()) << "TX acquire got bad non-root node"; + JLOG(journal_.warn()) << "TX acquire got bad non-root node " << d.first + << " for TX set " << hash_ << " from peer " << peer->id(); return SHAMapAddNode::invalid(); } } diff --git a/src/xrpld/app/ledger/detail/TransactionAcquire.h b/src/xrpld/app/ledger/detail/TransactionAcquire.h index 5b33066390..2faf74b557 100644 --- a/src/xrpld/app/ledger/detail/TransactionAcquire.h +++ b/src/xrpld/app/ledger/detail/TransactionAcquire.h @@ -6,10 +6,10 @@ #include #include -#include #include #include #include +#include #include #include @@ -32,8 +32,8 @@ public: SHAMapAddNode takeNodes( - std::vector> const& data, - std::shared_ptr const&); + std::vector> data, + std::shared_ptr const& peer); void init(int startPeers); diff --git a/src/xrpld/overlay/Peer.h b/src/xrpld/overlay/Peer.h index 23a45dc512..20a8730cf1 100644 --- a/src/xrpld/overlay/Peer.h +++ b/src/xrpld/overlay/Peer.h @@ -23,6 +23,7 @@ enum class ProtocolFeature { ValidatorListPropagation, ValidatorList2Propagation, LedgerReplay, + LedgerNodeDepth, }; /** diff --git a/src/xrpld/overlay/detail/PeerImp.cpp b/src/xrpld/overlay/detail/PeerImp.cpp index d7a9a9e449..688d0ac314 100644 --- a/src/xrpld/overlay/detail/PeerImp.cpp +++ b/src/xrpld/overlay/detail/PeerImp.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -61,6 +62,7 @@ #include #include #include +#include #include #include @@ -543,6 +545,8 @@ PeerImp::supportsFeature(ProtocolFeature f) const return protocol_ >= makeProtocol(2, 1); case ProtocolFeature::ValidatorList2Propagation: return protocol_ >= makeProtocol(2, 2); + case ProtocolFeature::LedgerNodeDepth: + return protocol_ >= makeProtocol(2, 3); case ProtocolFeature::LedgerReplay: return ledgerReplayEnabled_; } @@ -1477,23 +1481,12 @@ PeerImp::onMessage(std::shared_ptr const& m) } } - // Verify ledger node IDs - if (itype != protocol::liBASE) + // Verify ledger node counts. Full parsing of the node IDs is deferred to the job, so the I/O + // thread is not burdened with SHAMapNodeID deserialization for every TMGetLedger message. + if (itype != protocol::liBASE && m->nodeids_size() <= 0) { - if (m->nodeids_size() <= 0) - { - badData("Invalid ledger node IDs"); - return; - } - - for (auto const& nodeId : m->nodeids()) - { - if (deserializeSHAMapNodeID(nodeId) == std::nullopt) - { - badData("Invalid SHAMap node ID"); - return; - } - } + badData("Invalid ledger node IDs"); + return; } // Verify query type @@ -1513,11 +1506,57 @@ PeerImp::onMessage(std::shared_ptr const& m) } } - // Queue a job to process the request + // Queue a job to process the request. std::weak_ptr const weak = shared_from_this(); - app_.getJobQueue().addJob(JtLedgerReq, "RcvGetLedger", [weak, m]() { - if (auto peer = weak.lock()) - peer->processLedgerRequest(m); + app_.getJobQueue().addJob(JtLedgerReq, "RcvGetLedger", [weak, m, itype]() { + auto peer = weak.lock(); + if (!peer) + return; + + std::vector nodeIDs; + bool tooManyNodeIds = false; + if (itype != protocol::liBASE) + { + nodeIDs.reserve(std::min(m->nodeids_size(), Tuning::kSoftMaxReplyNodes)); + for (auto const& nodeId : m->nodeids()) + { + if (nodeIDs.size() >= Tuning::kSoftMaxReplyNodes) + { + // The peer requested too many node IDs. Continue processing the received node + // IDs up to the limit. If the request is legitimate then at least they will get + // a response and won't have to resend these nodes in their next request. + tooManyNodeIds = true; + break; + } + auto parsed = deserializeSHAMapNodeID(nodeId); + if (!parsed) + { + peer->charge(Resource::kFeeInvalidData, "TMGetLedger: Invalid node ID"); + return; + } + nodeIDs.push_back(std::move(*parsed)); + } + } + + // These are two distinct infractions and are charged independently: requesting too many + // node IDs is charged even for a relay response, while the base "get ledger request" charge + // below is skipped for relay responses. + if (tooManyNodeIds) + { + peer->charge(Resource::kFeeModerateBurdenPeer, "TMGetLedger: too many node IDs"); + + // Truncate the request to what was actually parsed and charged for, so that if this + // request ends up being relayed to another peer, we don't forward the oversized list. + m->mutable_nodeids()->DeleteSubrange( + static_cast(nodeIDs.size()), + m->nodeids_size() - static_cast(nodeIDs.size())); + } + if (!m->has_requestcookie()) + { + peer->charge(Resource::kFeeModerateBurdenPeer, "TMGetLedger: get ledger request"); + } + + peer->processLedgerRequest(m, std::move(nodeIDs)); }); } @@ -1682,12 +1721,119 @@ PeerImp::onMessage(std::shared_ptr const& m) return; } - // If there is a request cookie, attempt to relay the message + // If there is a request cookie, attempt to relay the message. if (m->has_requestcookie()) { if (auto peer = overlay_.findPeerByShortID(m->requestcookie())) { m->clear_requestcookie(); + + // If the original requester doesn't support the new depth-based format, rewrite any + // nodes that use it back to the legacy nodeid format before relaying. Once all nodes + // have upgraded, the old protocol version and this code can be removed. Make sure that + // the format of the nodes is consistent - either all use the legacy format or the new + // format, unless it is liBASE data in which case none of these fields should be set. + auto const peerSupportsNodeDepth = + peer->supportsFeature(ProtocolFeature::LedgerNodeDepth); + enum class MessageType { Unknown, Base, Legacy, Depth }; + MessageType messageType = MessageType::Unknown; + for (int i = 0; i < m->nodes_size(); ++i) + { + auto* ledgerNode = m->mutable_nodes(i); + + // All nodes should have non-empty data. The field is required so we don't need to + // check for presence first. + if (ledgerNode->nodedata().empty()) + { + badData( + "Received node with empty data while relaying ledger data for " + + to_string(uint256::fromRaw(m->ledgerhash())) + " to peer " + + std::to_string(peer->id())); + return; + } + + MessageType msgType = MessageType::Unknown; + if (m->type() == protocol::liBASE) + { + if (ledgerNode->has_nodeid() || ledgerNode->has_id() || ledgerNode->has_depth()) + { + badData( + "Received liBASE message with node reference while relaying ledger " + "data for " + + to_string(uint256::fromRaw(m->ledgerhash())) + " to peer " + + std::to_string(peer->id())); + return; + } + msgType = MessageType::Base; + } + else + { + msgType = ledgerNode->has_nodeid() ? MessageType::Legacy : MessageType::Depth; + } + if (messageType != MessageType::Unknown && messageType != msgType) + { + badData( + "Received mixed mode message while relaying ledger data for " + + to_string(uint256::fromRaw(m->ledgerhash())) + " to peer " + + std::to_string(peer->id())); + return; + } + messageType = msgType; + + if (peerSupportsNodeDepth || msgType != MessageType::Depth) + continue; + + SOMETIMES( + !peerSupportsNodeDepth, + "xrpl::PeerImp : relaying depth-format ledger data to pre-2.3 peer"); + switch (ledgerNode->reference_case()) + { + case protocol::TMLedgerNode::kId: { + // We can directly copy the `id` field, because it uses the same wire format + // as the legacy `nodeid` field. + REACHABLE("xrpl::PeerImp : relay downgrade id to nodeid"); + ledgerNode->set_nodeid(ledgerNode->id()); + ledgerNode->clear_id(); + break; + } + case protocol::TMLedgerNode::kDepth: { + // We need to regenerate the node ID from the node data and depth. + auto treeNode = getTreeNode(ledgerNode->nodedata()); + if (!treeNode) + { + badData( + "Unable to get tree node while relaying ledger data for " + + to_string(uint256::fromRaw(m->ledgerhash())) + " to peer " + + std::to_string(peer->id())); + return; + } + + auto const nodeID = getSHAMapNodeID(*ledgerNode, *treeNode); + if (!nodeID) + { + badData( + "Unable to get node ID while relaying ledger data for " + + to_string(uint256::fromRaw(m->ledgerhash())) + " to peer " + + std::to_string(peer->id())); + return; + } + + REACHABLE("xrpl::PeerImp : relay downgrade depth to nodeid"); + ledgerNode->set_nodeid(nodeID->getRawString()); + ledgerNode->clear_depth(); + break; + } + default: { + SOMETIMES(true, "xrpl::PeerImp : relay node has empty reference"); + badData( + "Empty node reference while relaying ledger data for " + + to_string(uint256::fromRaw(m->ledgerhash())) + " to peer " + + std::to_string(peer->id())); + return; + } + } + } + peer->send(std::make_shared(*m, protocol::mtLEDGER_DATA)); } else @@ -3287,12 +3433,10 @@ PeerImp::getTxSet(std::shared_ptr const& m) const } void -PeerImp::processLedgerRequest(std::shared_ptr const& m) +PeerImp::processLedgerRequest( + std::shared_ptr const& m, + std::vector nodeIDs) { - // Do not resource charge a peer responding to a relay - if (!m->has_requestcookie()) - charge(Resource::kFeeModerateBurdenPeer, "received a get ledger request"); - std::shared_ptr ledger; std::shared_ptr sharedMap; SHAMap const* map{nullptr}; @@ -3372,26 +3516,25 @@ PeerImp::processLedgerRequest(std::shared_ptr const& m) } // Add requested node data to reply - if (m->nodeids_size() > 0) + if (!nodeIDs.empty()) { std::uint32_t const defaultDepth = isHighLatency() ? 2 : 1; auto const queryDepth{m->has_querydepth() ? m->querydepth() : defaultDepth}; - std::vector> data; + std::vector data; + data.reserve(Tuning::kSoftMaxReplyNodes); + auto const useLedgerNodeDepth = supportsFeature(ProtocolFeature::LedgerNodeDepth); - for (int i = 0; - i < m->nodeids_size() && ledgerData.nodes_size() < Tuning::kSoftMaxReplyNodes; - ++i) + for (auto const& nodeID : nodeIDs) { - auto const shaMapNodeId{deserializeSHAMapNodeID(m->nodeids(i))}; + if (ledgerData.nodes_size() >= Tuning::kSoftMaxReplyNodes) + break; data.clear(); - data.reserve(Tuning::kSoftMaxReplyNodes); try { - // NOLINTNEXTLINE(bugprone-unchecked-optional-access) nodeids checked in onGetLedger - if (map->getNodeFat(*shaMapNodeId, data, fatLeaves, queryDepth)) + if (map->getNodeFat(nodeID, data, fatLeaves, queryDepth)) { JLOG(pJournal_.trace()) << "processLedgerRequest: getNodeFat got " << data.size() << " nodes"; @@ -3400,9 +3543,27 @@ PeerImp::processLedgerRequest(std::shared_ptr const& m) { if (ledgerData.nodes_size() >= Tuning::kHardMaxReplyNodes) break; + protocol::TMLedgerNode* node{ledgerData.add_nodes()}; - node->set_nodeid(d.first.getRawString()); - node->set_nodedata(d.second.data(), d.second.size()); + node->set_nodedata(d.data.data(), d.data.size()); + + // When the LedgerNodeDepth protocol feature is not supported by the peer, + // we always set the `nodeid` field. However, when it is supported then we + // set the `id` field for inner nodes and the `depth` field for leaf nodes. + if (!useLedgerNodeDepth) + { + node->set_nodeid(d.nodeID.getRawString()); + } + else if (d.isLeaf) + { + REACHABLE("xrpl::PeerImp : emit leaf depth in reply"); + node->set_depth(d.nodeID.getDepth()); + } + else + { + REACHABLE("xrpl::PeerImp : emit inner id in reply"); + node->set_id(d.nodeID.getRawString()); + } } } else @@ -3441,13 +3602,13 @@ PeerImp::processLedgerRequest(std::shared_ptr const& m) info += ", no hash specified"; JLOG(pJournal_.warn()) - << "processLedgerRequest: getNodeFat with nodeId " << *shaMapNodeId + << "processLedgerRequest: getNodeFat with nodeId " << nodeID << " and ledger info type " << info << " throws exception: " << e.what(); } } JLOG(pJournal_.info()) << "processLedgerRequest: Got request for " << m->nodeids_size() - << " nodes at depth " << queryDepth << ", return " + << " node IDs at depth " << queryDepth << ", return " << ledgerData.nodes_size() << " nodes"; } diff --git a/src/xrpld/overlay/detail/PeerImp.h b/src/xrpld/overlay/detail/PeerImp.h index 90f8a917f4..de90e60955 100644 --- a/src/xrpld/overlay/detail/PeerImp.h +++ b/src/xrpld/overlay/detail/PeerImp.h @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -679,7 +680,9 @@ private: getTxSet(std::shared_ptr const& m) const; void - processLedgerRequest(std::shared_ptr const& m); + processLedgerRequest( + std::shared_ptr const& m, + std::vector nodeIDs); protected: // Kept `protected` so test subclasses (see diff --git a/src/xrpld/overlay/detail/ProtocolVersion.cpp b/src/xrpld/overlay/detail/ProtocolVersion.cpp index 347e59accb..2d5d0a56f7 100644 --- a/src/xrpld/overlay/detail/ProtocolVersion.cpp +++ b/src/xrpld/overlay/detail/ProtocolVersion.cpp @@ -29,6 +29,7 @@ namespace xrpl { constexpr ProtocolVersion const kSupportedProtocolList[]{ {2, 1}, {2, 2}, + {2, 3}, }; // This ugly construct ensures that supportedProtocolList is sorted in strictly From 85e73cbd32b9112ca5c524f256cb3581b86dd8c1 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Thu, 30 Jul 2026 20:28:31 +0100 Subject: [PATCH 06/32] ci: Run coverage first in CI (#7917) --- .github/scripts/strategy-matrix/linux.json | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/scripts/strategy-matrix/linux.json b/.github/scripts/strategy-matrix/linux.json index 60f3da09f1..9510212344 100644 --- a/.github/scripts/strategy-matrix/linux.json +++ b/.github/scripts/strategy-matrix/linux.json @@ -2,12 +2,21 @@ "image_tag": "sha-fecfc0c", "configs": { "ubuntu": [ + { + "compiler": ["gcc"], + "build_type": ["Debug"], + "arch": ["amd64"], + "minimal": true, + "suffix": "coverage", + "extra_cmake_args": "-DUNIT_TEST_REFERENCE_FEE=500 -Dcoverage=ON -Dcoverage_format=xml -DCODE_COVERAGE_VERBOSE=ON -DCMAKE_C_FLAGS=-O0 -DCMAKE_CXX_FLAGS=-O0" + }, { "compiler": ["clang"], "build_type": ["Release"], "arch": ["amd64"], "minimal": true }, + { "compiler": ["gcc"], "build_type": ["Release"], @@ -29,14 +38,6 @@ "sanitizers": ["address", "undefinedbehavior"] }, - { - "compiler": ["gcc"], - "build_type": ["Debug"], - "arch": ["amd64"], - "minimal": true, - "suffix": "coverage", - "extra_cmake_args": "-DUNIT_TEST_REFERENCE_FEE=500 -Dcoverage=ON -Dcoverage_format=xml -DCODE_COVERAGE_VERBOSE=ON -DCMAKE_C_FLAGS=-O0 -DCMAKE_CXX_FLAGS=-O0" - }, { "compiler": ["clang"], "build_type": ["Debug"], From ecdd457f3598c7286a9af4aff358fbd30039173f Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Fri, 31 Jul 2026 00:04:38 +0100 Subject: [PATCH 07/32] chore: Gtest migration followups first pass (#7884) --- cmake/XrplAddBenchmark.cmake | 2 + src/benchmarks/libxrpl/nodestore/Backend.cpp | 39 +-- .../libxrpl/nodestore/NodeStoreBench.h | 31 +-- src/tests/libxrpl/basics/IntrusiveShared.cpp | 240 +++++++++++------- src/tests/libxrpl/basics/MallocTrim.cpp | 2 +- src/tests/libxrpl/basics/Number.cpp | 16 +- src/tests/libxrpl/basics/base58.cpp | 22 +- src/tests/libxrpl/basics/base_uint.cpp | 104 ++++---- src/tests/libxrpl/basics/join.cpp | 4 +- .../libxrpl/consensus/CensorshipDetector.cpp | 2 +- src/tests/libxrpl/csf/TrustGraph.h | 4 +- src/tests/libxrpl/csf/random.h | 9 +- src/tests/libxrpl/nodestore/Database.cpp | 6 +- src/tests/libxrpl/resource/Logic.cpp | 42 +-- src/tests/libxrpl/shamap/SHAMap.cpp | 17 +- src/tests/libxrpl/shamap/SHAMapSync.cpp | 29 ++- 16 files changed, 315 insertions(+), 254 deletions(-) diff --git a/cmake/XrplAddBenchmark.cmake b/cmake/XrplAddBenchmark.cmake index 1dd875dd61..921deb0658 100644 --- a/cmake/XrplAddBenchmark.cmake +++ b/cmake/XrplAddBenchmark.cmake @@ -1,3 +1,5 @@ +include_guard() + include(isolate_headers) # Define a benchmark executable for the module `name`. diff --git a/src/benchmarks/libxrpl/nodestore/Backend.cpp b/src/benchmarks/libxrpl/nodestore/Backend.cpp index f854dbd3a7..cd3e15bd65 100644 --- a/src/benchmarks/libxrpl/nodestore/Backend.cpp +++ b/src/benchmarks/libxrpl/nodestore/Backend.cpp @@ -20,21 +20,22 @@ namespace xrpl::node_store { namespace { -constexpr std::size_t kPoolSizes[] = {1000, 10000, 100000}; -constexpr int kThreadCounts[] = {1, 4, 8}; +constexpr auto kPoolSizes = std::to_array({1000, 10000, 100000}); +constexpr auto kThreadCounts = std::to_array({1, 4, 8}); constexpr std::size_t kBatchSize = 256; +constexpr std::size_t kMissRatio = 5; constexpr std::string_view kNamePrefix = "BM_Backend_"; constexpr std::string_view kNameSeparator = "/"; struct RunState { - std::unique_ptr harness; - Batch present; // prefix-1 objects, eligible to be stored - Batch recent; // prefix-1 objects in the "future" key space - std::vector missing; // prefix-2 keys that are never stored - std::vector shuffle; // [0, poolSize) permutation for random-like access - std::size_t avgPayload = 0; // mean getData().size() over `present` + std::unique_ptr harness; ///< backend under test, rebuilt per run + Batch present; ///< prefix-1 objects, eligible to be stored + Batch recent; ///< prefix-1 objects in the "future" key space + std::vector missing; ///< prefix-2 keys that are never stored + std::vector shuffle; ///< [0, poolSize) permutation for random-like access + std::size_t avgPayload = 0; ///< mean getData().size() over `present` void release() @@ -85,7 +86,7 @@ Workload const kInsert{ }, .iterate = [](IterateContext const& ctx) { - auto& [rs, backend, index, poolSize] = ctx; + auto const& [rs, backend, index, poolSize] = ctx; backend.store(rs.present[index % poolSize]); }, .reportBytes = true, @@ -104,7 +105,7 @@ Workload const kFetch{ }, .iterate = [](IterateContext const& ctx) { - auto& [rs, backend, index, poolSize] = ctx; + auto const& [rs, backend, index, poolSize] = ctx; std::shared_ptr result; backend.fetch(rs.present[index % poolSize]->getHash(), &result); benchmark::DoNotOptimize(result); @@ -118,7 +119,7 @@ Workload const kMissing{ .setup = [](SetupContext const& ctx) { ctx.rs.missing = makeMissingKeys(ctx.poolSize); }, .iterate = [](IterateContext const& ctx) { - auto& [rs, backend, index, poolSize] = ctx; + auto const& [rs, backend, index, poolSize] = ctx; std::shared_ptr result; backend.fetch(rs.missing[index % poolSize], &result); benchmark::DoNotOptimize(result); @@ -139,10 +140,10 @@ Workload const kMixed{ }, .iterate = [](IterateContext const& ctx) { - auto& [rs, backend, index, poolSize] = ctx; + auto const& [rs, backend, index, poolSize] = ctx; std::shared_ptr result; auto const pick = rs.shuffle[index % poolSize]; - if (index % 5 == 0) + if (index % kMissRatio == 0) { backend.fetch(rs.missing[pick], &result); } @@ -170,7 +171,7 @@ Workload const kWork{ }, .iterate = [](IterateContext const& ctx) { - auto& [rs, backend, index, poolSize] = ctx; + auto const& [rs, backend, index, poolSize] = ctx; auto const slot = index % poolSize; auto const pick = rs.shuffle[slot]; @@ -239,7 +240,7 @@ registerWorkload(BackendConfig const& bc, Workload const& w) { auto rs = std::make_shared(); auto* b = benchmark::RegisterBenchmark(name, makeRunner(w, cfg, rs)); - b->RangeMultiplier(10)->Range(kPoolSizes[0], kPoolSizes[std::size(kPoolSizes) - 1]); + b->RangeMultiplier(10)->Range(kPoolSizes.front(), kPoolSizes.back()); b->Threads(1)->Threads(4)->Threads(8)->UseRealTime(); return; @@ -249,14 +250,14 @@ registerWorkload(BackendConfig const& bc, Workload const& w) { for (auto const threads : kThreadCounts) { - if (poolSize % static_cast(threads) != 0) + if (poolSize % threads != 0) continue; auto rs = std::make_shared(); benchmark::RegisterBenchmark(name, makeRunner(w, cfg, rs)) ->Arg(poolSize) - ->Iterations(poolSize / static_cast(threads)) - ->Threads(threads) + ->Iterations(poolSize / threads) + ->Threads(static_cast(threads)) ->UseRealTime(); } } @@ -289,7 +290,7 @@ registerStoreBatch(BackendConfig const& bc) rs->harness = std::make_unique(cfg); rs->present = makePool(1, poolSize); rs->avgPayload = averagePayload(rs->present); - std::vector const batches = sliceBatches(rs->present, kBatchSize); + std::vector const batches = sliceFixedBatches(rs->present, kBatchSize); if (batches.empty()) { state.SkipWithError("pool smaller than one batch"); diff --git a/src/benchmarks/libxrpl/nodestore/NodeStoreBench.h b/src/benchmarks/libxrpl/nodestore/NodeStoreBench.h index 57abf42e89..debdc5d47a 100644 --- a/src/benchmarks/libxrpl/nodestore/NodeStoreBench.h +++ b/src/benchmarks/libxrpl/nodestore/NodeStoreBench.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -40,18 +41,13 @@ inline void rngcpy(void* buffer, std::size_t bytes, Generator& g) { using result_type = typename Generator::result_type; - while (bytes >= sizeof(result_type)) + while (bytes > 0) { auto const v = g(); - std::memcpy(buffer, &v, sizeof(v)); - buffer = reinterpret_cast(buffer) + sizeof(v); - bytes -= sizeof(v); - } - - if (bytes > 0) - { - auto const v = g(); - std::memcpy(buffer, &v, bytes); + auto const chunk = std::min(bytes, sizeof(result_type)); + std::memcpy(buffer, &v, chunk); + buffer = reinterpret_cast(buffer) + chunk; + bytes -= chunk; } } @@ -145,7 +141,7 @@ makePool(std::uint8_t prefix, std::size_t count, std::size_t start = 0) Sequence seq(prefix); Batch pool; pool.reserve(count); - for (std::size_t i = 0; i < count; ++i) + for (auto i = 0uz; i < count; ++i) pool.push_back(seq.obj(start + i)); return pool; } @@ -158,7 +154,7 @@ makeMissingKeys(std::size_t count) Sequence seq(2); std::vector keys; keys.reserve(count); - for (std::size_t i = 0; i < count; ++i) + for (auto i = 0uz; i < count; ++i) keys.push_back(seq.key(i)); return keys; } @@ -206,16 +202,16 @@ inline std::vector makeShuffle(std::size_t size, std::uint64_t seed) { std::vector v(size); - std::iota(v.begin(), v.end(), std::size_t{0}); + std::ranges::iota(v, 0uz); beast::xor_shift_engine gen(seed); - std::shuffle(v.begin(), v.end(), gen); + std::ranges::shuffle(v, gen); return v; } // Partition a pool into fixed-size batches. Any trailing remainder shorter than // `batchSize` is dropped, so every returned batch has exactly `batchSize`. inline std::vector -sliceBatches(Batch const& pool, std::size_t batchSize) +sliceFixedBatches(Batch const& pool, std::size_t batchSize) { std::vector batches; if (batchSize == 0) @@ -228,13 +224,10 @@ sliceBatches(Batch const& pool, std::size_t batchSize) /** * @brief RAII owner of a NodeStore Backend opened on a private temporary directory. - * - * Member declaration order matters: `tempDir` is declared first so it is - * destroyed last, after the backend has closed and released its files. */ struct BackendHarness { - beast::TempDir tempDir; + beast::TempDir tempDir; ///< Declared first so it is destroyed last DummyScheduler scheduler; beast::Journal journal{beast::Journal::getNullSink()}; std::unique_ptr backend; diff --git a/src/tests/libxrpl/basics/IntrusiveShared.cpp b/src/tests/libxrpl/basics/IntrusiveShared.cpp index e798cd1ccc..b9f8930b7b 100644 --- a/src/tests/libxrpl/basics/IntrusiveShared.cpp +++ b/src/tests/libxrpl/basics/IntrusiveShared.cpp @@ -50,11 +50,11 @@ struct Barrier { std::mutex mtx; std::condition_variable cv; - int count; - int const initial; + std::size_t count; + std::size_t const initial; std::size_t generation{0}; - explicit Barrier(int n) : count(n), initial(n) + explicit Barrier(std::size_t n) : count(n), initial(n) { } @@ -217,7 +217,7 @@ TEST(IntrusiveSharedTest, basics) auto id = b->id; EXPECT_EQ(TIBase::getState(id), Alive); EXPECT_EQ(b->useCount(), 1); - for (int i = 0; i < 10; ++i) + for (auto i = 0uz; i < 10; ++i) { strong.push_back(b); } @@ -232,7 +232,7 @@ TEST(IntrusiveSharedTest, basics) id = b->id; EXPECT_EQ(TIBase::getState(id), Alive); EXPECT_EQ(b->useCount(), 1); - for (int i = 0; i < 10; ++i) + for (auto i = 0uz; i < 10; ++i) { weak.emplace_back(b); EXPECT_EQ(b->useCount(), 1); @@ -280,17 +280,17 @@ TEST(IntrusiveSharedTest, basics) TIBase::ResetStatesGuard const rsg{true}; using enum TrackedState; - using swu = SharedWeakUnion; - swu b = makeSharedIntrusive(); + using SharedWeak = SharedWeakUnion; + SharedWeak b = makeSharedIntrusive(); EXPECT_TRUE(b.isStrong() && b.useCount() == 1); auto id = b.get()->id; EXPECT_EQ(TIBase::getState(id), Alive); - swu w = b; + SharedWeak w = b; EXPECT_TRUE(TIBase::getState(id) == Alive); EXPECT_TRUE(w.isStrong() && b.useCount() == 2); w.convertToWeak(); EXPECT_TRUE(w.isWeak() && b.useCount() == 1); - swu s = w; + SharedWeak s = w; EXPECT_TRUE(s.isWeak() && b.useCount() == 1); s.convertToStrong(); EXPECT_TRUE(s.isStrong() && b.useCount() == 2); @@ -380,43 +380,57 @@ TEST(IntrusiveSharedTest, partial_delete) std::atomic destructorRan{false}; std::atomic partialDeleteRan{false}; std::latch partialDeleteStartedSyncPoint{2}; + strong->tracingCallback = [&](TrackedState cur, std::optional next) { using enum TrackedState; - if (next == DeletedStarted) + if (!next) + return; + + switch (*next) { - // strong goes out of scope while weak is still in scope - // This checks that partialDelete has run to completion - // before the destructor is called. A sleep is inserted - // inside the partial delete to make sure the destructor is - // given an opportunity to run during partial delete. - EXPECT_EQ(cur, PartiallyDeleted); - } - if (next == PartiallyDeletedStarted) - { - partialDeleteStartedSyncPoint.arrive_and_wait(); - using namespace std::chrono_literals; - // Sleep and let the weak pointer go out of scope, - // potentially triggering a destructor while partial delete - // is running. The test is to make sure that doesn't happen. - std::this_thread::sleep_for(800ms); - } - if (next == PartiallyDeleted) - { - EXPECT_FALSE(partialDeleteRan.exchange(true) || destructorRan.load()); - } - if (next == Deleted) - { - EXPECT_FALSE(destructorRan.exchange(true)); + case DeletedStarted: + // strong goes out of scope while weak is still in scope + // This checks that partialDelete has run to completion + // before the destructor is called. A sleep is inserted + // inside the partial delete to make sure the destructor is + // given an opportunity to run during partial delete. + EXPECT_EQ(cur, PartiallyDeleted); + break; + + case PartiallyDeletedStarted: { + partialDeleteStartedSyncPoint.arrive_and_wait(); + using namespace std::chrono_literals; + // Sleep and let the weak pointer go out of scope, + // potentially triggering a destructor while partial delete + // is running. The test is to make sure that doesn't happen. + std::this_thread::sleep_for(800ms); + break; + } + + case PartiallyDeleted: + EXPECT_FALSE(partialDeleteRan.exchange(true) || destructorRan.load()); + break; + + case Deleted: + EXPECT_FALSE(destructorRan.exchange(true)); + break; + + case Uninitialized: + case Alive: + break; } }; + std::thread t1{[&] { partialDeleteStartedSyncPoint.arrive_and_wait(); weak.reset(); // Trigger a full delete as soon as the partial // delete starts }}; + std::thread t2{[&] { strong.reset(); // Trigger a partial delete }}; + t1.join(); t2.join(); @@ -444,13 +458,24 @@ TEST(IntrusiveSharedTest, destructor) std::latch weakResetSyncPoint{2}; strong->tracingCallback = [&](TrackedState cur, std::optional next) { using enum TrackedState; - if (next == PartiallyDeleted) + if (!next) + return; + + switch (*next) { - EXPECT_FALSE(partialDeleteRan.exchange(true) || destructorRan.load()); - } - if (next == Deleted) - { - EXPECT_FALSE(destructorRan.exchange(true)); + case PartiallyDeleted: + EXPECT_FALSE(partialDeleteRan.exchange(true) || destructorRan.load()); + break; + + case Deleted: + EXPECT_FALSE(destructorRan.exchange(true)); + break; + + case Uninitialized: + case Alive: + case PartiallyDeletedStarted: + case DeletedStarted: + break; } }; std::thread t1{[&] { @@ -492,25 +517,36 @@ TEST(IntrusiveSharedTest, multithreaded_clear_mixed_variant) auto tracingCallback = [&](TrackedState cur, std::optional next) { using enum TrackedState; auto [destructorRan, partialDeleteRan] = getDestructorState(); - if (next == PartiallyDeleted) + if (!next) + return; + + switch (*next) { - EXPECT_FALSE(partialDeleteRan || destructorRan); - setPartialDeleteRan(); - } - if (next == Deleted) - { - EXPECT_FALSE(destructorRan); - setDestructorRan(); + case PartiallyDeleted: + EXPECT_FALSE(partialDeleteRan || destructorRan); + setPartialDeleteRan(); + break; + + case Deleted: + EXPECT_FALSE(destructorRan); + setDestructorRan(); + break; + + case Uninitialized: + case Alive: + case PartiallyDeletedStarted: + case DeletedStarted: + break; } }; auto createVecOfPointers = [&](auto const& toClone, std::default_random_engine& eng) -> std::vector, WeakIntrusive>> { std::vector, WeakIntrusive>> result; - std::uniform_int_distribution<> toCreateDist(4, 64); + std::uniform_int_distribution toCreateDist(4, 64); std::uniform_int_distribution<> isStrongDist(0, 1); auto numToCreate = toCreateDist(eng); result.reserve(numToCreate); - for (int i = 0; i < numToCreate; ++i) + for (auto i = 0uz; i < numToCreate; ++i) { if (isStrongDist(eng)) { @@ -523,8 +559,8 @@ TEST(IntrusiveSharedTest, multithreaded_clear_mixed_variant) } return result; }; - constexpr int kLoopIters = 2 * 1024; - constexpr int kNumThreads = 16; + constexpr auto kLoopIters = 2uz * 1024; + constexpr auto kNumThreads = 16uz; std::vector> toClone; Barrier loopStartSyncPoint{kNumThreads}; Barrier postCreateToCloneSyncPoint{kNumThreads}; @@ -533,7 +569,7 @@ TEST(IntrusiveSharedTest, multithreaded_clear_mixed_variant) std::random_device rd; std::vector result; result.reserve(kNumThreads); - for (int i = 0; i < kNumThreads; ++i) + for (auto i = 0uz; i < kNumThreads; ++i) result.emplace_back(rd()); return result; }(); @@ -541,8 +577,8 @@ TEST(IntrusiveSharedTest, multithreaded_clear_mixed_variant) // cloneAndDestroy clones the strong pointer into a vector of mixed // strong and weak pointers and destroys them all at once. // threadId==0 is special. - auto cloneAndDestroy = [&](int threadId) { - for (int i = 0; i < kLoopIters; ++i) + auto cloneAndDestroy = [&](std::size_t threadId) { + for (auto i = 0uz; i < kLoopIters; ++i) { // ------ Sync Point ------ loopStartSyncPoint.arriveAndWait(); @@ -582,11 +618,11 @@ TEST(IntrusiveSharedTest, multithreaded_clear_mixed_variant) }; std::vector threads; threads.reserve(kNumThreads); - for (int i = 0; i < kNumThreads; ++i) + for (auto i = 0uz; i < kNumThreads; ++i) { threads.emplace_back(cloneAndDestroy, i); } - for (int i = 0; i < kNumThreads; ++i) + for (auto i = 0uz; i < kNumThreads; ++i) { threads[i].join(); } @@ -623,31 +659,42 @@ TEST(IntrusiveSharedTest, multithreaded_clear_mixed_union) auto tracingCallback = [&](TrackedState cur, std::optional next) { using enum TrackedState; auto [destructorRan, partialDeleteRan] = getDestructorState(); - if (next == PartiallyDeleted) + if (!next) + return; + + switch (*next) { - EXPECT_FALSE(partialDeleteRan || destructorRan); - setPartialDeleteRan(); - } - if (next == Deleted) - { - EXPECT_FALSE(destructorRan); - setDestructorRan(); + case PartiallyDeleted: + EXPECT_FALSE(partialDeleteRan || destructorRan); + setPartialDeleteRan(); + break; + + case Deleted: + EXPECT_FALSE(destructorRan); + setDestructorRan(); + break; + + case Uninitialized: + case Alive: + case PartiallyDeletedStarted: + case DeletedStarted: + break; } }; auto createVecOfPointers = [&](auto const& toClone, std::default_random_engine& eng) -> std::vector> { std::vector> result; - std::uniform_int_distribution<> toCreateDist(4, 64); + std::uniform_int_distribution toCreateDist(4, 64); auto numToCreate = toCreateDist(eng); result.reserve(numToCreate); - for (int i = 0; i < numToCreate; ++i) + for (auto i = 0uz; i < numToCreate; ++i) result.emplace_back(SharedIntrusive(toClone)); return result; }; - constexpr int kLoopIters = 2 * 1024; - constexpr int kFlipPointersLoopIters = 256; - constexpr int kNumThreads = 16; + constexpr auto kLoopIters = 2uz * 1024; + constexpr auto kFlipPointersLoopIters = 256uz; + constexpr auto kNumThreads = 16uz; std::vector> toClone; Barrier loopStartSyncPoint{kNumThreads}; Barrier postCreateToCloneSyncPoint{kNumThreads}; @@ -657,7 +704,7 @@ TEST(IntrusiveSharedTest, multithreaded_clear_mixed_union) std::random_device rd; std::vector result; result.reserve(kNumThreads); - for (int i = 0; i < kNumThreads; ++i) + for (auto i = 0uz; i < kNumThreads; ++i) result.emplace_back(rd()); return result; }(); @@ -666,8 +713,8 @@ TEST(IntrusiveSharedTest, multithreaded_clear_mixed_union) // mixed strong and weak pointers, runs a loop that randomly // changes strong pointers to weak pointers, and destroys them // all at once. - auto cloneAndDestroy = [&](int threadId) { - for (int i = 0; i < kLoopIters; ++i) + auto cloneAndDestroy = [&](std::size_t threadId) { + for (auto i = 0uz; i < kLoopIters; ++i) { // ------ Sync Point ------ loopStartSyncPoint.arriveAndWait(); @@ -702,7 +749,7 @@ TEST(IntrusiveSharedTest, multithreaded_clear_mixed_union) postCreateVecOfPointersSyncPoint.arriveAndWait(); std::uniform_int_distribution<> isStrongDist(0, 1); - for (int f = 0; f < kFlipPointersLoopIters; ++f) + for (auto f = 0uz; f < kFlipPointersLoopIters; ++f) { for (auto& p : v) { @@ -725,11 +772,11 @@ TEST(IntrusiveSharedTest, multithreaded_clear_mixed_union) }; std::vector threads; threads.reserve(kNumThreads); - for (int i = 0; i < kNumThreads; ++i) + for (auto i = 0uz; i < kNumThreads; ++i) { threads.emplace_back(cloneAndDestroy, i); } - for (int i = 0; i < kNumThreads; ++i) + for (auto i = 0uz; i < kNumThreads; ++i) { threads[i].join(); } @@ -761,21 +808,32 @@ TEST(IntrusiveSharedTest, multithreaded_locking_weak) auto tracingCallback = [&](TrackedState cur, std::optional next) { using enum TrackedState; auto [destructorRan, partialDeleteRan] = getDestructorState(); - if (next == PartiallyDeleted) + if (!next) + return; + + switch (*next) { - EXPECT_FALSE(partialDeleteRan || destructorRan); - setPartialDeleteRan(); - } - if (next == Deleted) - { - EXPECT_FALSE(destructorRan); - setDestructorRan(); + case PartiallyDeleted: + EXPECT_FALSE(partialDeleteRan || destructorRan); + setPartialDeleteRan(); + break; + + case Deleted: + EXPECT_FALSE(destructorRan); + setDestructorRan(); + break; + + case Uninitialized: + case Alive: + case PartiallyDeletedStarted: + case DeletedStarted: + break; } }; - constexpr int kLoopIters = 2 * 1024; - constexpr int kLockWeakLoopIters = 256; - constexpr int kNumThreads = 16; + constexpr auto kLoopIters = 2uz * 1024; + constexpr auto kLockWeakLoopIters = 256uz; + constexpr auto kNumThreads = 16uz; std::vector> toLock; Barrier loopStartSyncPoint{kNumThreads}; Barrier postCreateToLockSyncPoint{kNumThreads}; @@ -784,8 +842,8 @@ TEST(IntrusiveSharedTest, multithreaded_locking_weak) // lockAndDestroy creates weak pointers from the strong pointer // and runs a loop that locks the weak pointer. At the end of the loop // all the pointers are destroyed all at once. - auto lockAndDestroy = [&](int threadId) { - for (int i = 0; i < kLoopIters; ++i) + auto lockAndDestroy = [&](std::size_t threadId) { + for (auto i = 0uz; i < kLoopIters; ++i) { // ------ Sync Point ------ loopStartSyncPoint.arriveAndWait(); @@ -816,7 +874,7 @@ TEST(IntrusiveSharedTest, multithreaded_locking_weak) // Multiple threads all create a weak pointer from the same // strong pointer WeakIntrusive const weak{toLock[threadId]}; - for (int wi = 0; wi < kLockWeakLoopIters; ++wi) + for (auto wi = 0uz; wi < kLockWeakLoopIters; ++wi) { EXPECT_FALSE(weak.expired()); auto strong = weak.lock(); @@ -831,11 +889,11 @@ TEST(IntrusiveSharedTest, multithreaded_locking_weak) }; std::vector threads; threads.reserve(kNumThreads); - for (int i = 0; i < kNumThreads; ++i) + for (auto i = 0uz; i < kNumThreads; ++i) { threads.emplace_back(lockAndDestroy, i); } - for (int i = 0; i < kNumThreads; ++i) + for (auto i = 0uz; i < kNumThreads; ++i) { threads[i].join(); } diff --git a/src/tests/libxrpl/basics/MallocTrim.cpp b/src/tests/libxrpl/basics/MallocTrim.cpp index 6ac8957f0e..52151262b0 100644 --- a/src/tests/libxrpl/basics/MallocTrim.cpp +++ b/src/tests/libxrpl/basics/MallocTrim.cpp @@ -199,7 +199,7 @@ TEST(mallocTrim, repeated_calls) beast::Journal const journal{beast::Journal::getNullSink()}; // Call malloc_trim multiple times to ensure it's safe - for (int i = 0; i < 5; ++i) + for (auto i = 0uz; i < 5; ++i) { MallocTrimReport const report = mallocTrim("iteration_" + std::to_string(i), journal); diff --git a/src/tests/libxrpl/basics/Number.cpp b/src/tests/libxrpl/basics/Number.cpp index 36e1b4a700..32f93eb1f7 100644 --- a/src/tests/libxrpl/basics/Number.cpp +++ b/src/tests/libxrpl/basics/Number.cpp @@ -323,11 +323,11 @@ TEST(NumberTest, add) __LINE__, }, { - // Does not round. Mantissas are going to be > maxRep, so if + // Does not round. Mantissas are going to be > kMaxRep, so if // added together as uint64_t's, the result will overflow. // With addition using uint128_t, there's no problem. After // normalizing, the resulting mantissa ends up less than - // maxRep. + // kMaxRep. Number{false, 9'999'999'999'999'999'990ULL, 0, Number::Normalized{}}, Number{false, 9'999'999'999'999'999'990ULL, 0, Number::Normalized{}}, Number{false, 1'999'999'999'999'999'998ULL, 1, Number::Normalized{}}, @@ -1078,14 +1078,6 @@ TEST(NumberTest, root) EXPECT_EQ(result, z) << ss.str(); } }; - /* - auto tests = [&](auto const& cSmall, auto const& cLarge) { - test(cSmall); - if (scale != MantissaRange::mantissa_scale::small) - test(cLarge); - }; - */ - auto const cSmall = std::to_array( {{Number{2}, 2, Number{1414213562373095049, -18}}, {Number{2'000'000}, 2, Number{1414213562373095049, -15}}, @@ -1511,7 +1503,7 @@ TEST(NumberTest, to_string) NumberRoundModeGuard const mg(Number::RoundingMode::TowardsZero); auto const maxMantissa = Number::maxMantissa(); - EXPECT_EQ(maxMantissa, (9'999'999'999'999'999)); + EXPECT_EQ(maxMantissa, 9'999'999'999'999'999); test( Number{false, (maxMantissa * 1000) + 999, -3, Number::Normalized()}, "9999999999999999", @@ -1550,7 +1542,7 @@ TEST(NumberTest, to_string) NumberRoundModeGuard const mg(Number::RoundingMode::TowardsZero); auto const maxMantissa = Number::maxMantissa(); - EXPECT_EQ((maxMantissa), (9'999'999'999'999'999'999ULL)); + EXPECT_EQ(maxMantissa, 9'999'999'999'999'999'999ULL); test( Number{false, maxMantissa, 0, Number::Normalized{}}, "9999999999999999990", diff --git a/src/tests/libxrpl/basics/base58.cpp b/src/tests/libxrpl/basics/base58.cpp index d452453f76..d6b1d2c3f9 100644 --- a/src/tests/libxrpl/basics/base58.cpp +++ b/src/tests/libxrpl/basics/base58.cpp @@ -151,7 +151,7 @@ randomBigInt(std::uint8_t minSize = 1, std::uint8_t maxSize = 5) auto const numCoeff = numCoeffDist(eng); std::vector coeffs; coeffs.reserve(numCoeff); - for (int i = 0; i < numCoeff; ++i) + for (auto i = 0uz; i < numCoeff; ++i) { coeffs.push_back(dist(eng)); } @@ -167,7 +167,7 @@ TEST(Base58Test, multiprecision) auto eng = randEngine(); std::uniform_int_distribution dist; std::uniform_int_distribution dist1(1); - for (int i = 0; i < kIters; ++i) + for (auto i = 0uz; i < kIters; ++i) { std::uint64_t const d = dist(eng); if (d == 0u) @@ -185,7 +185,7 @@ TEST(Base58Test, multiprecision) EXPECT_EQ(refMod.convert_to(), mod); EXPECT_EQ(foundDiv, refDiv); } - for (int i = 0; i < kIters; ++i) + for (auto i = 0uz; i < kIters; ++i) { std::uint64_t const d = dist(eng); auto bigInt = multiprecision_utils::randomBigInt(/*minSize*/ 2); @@ -204,7 +204,7 @@ TEST(Base58Test, multiprecision) auto const foundAdd = multiprecision_utils::toBoostMP(bigInt); EXPECT_EQ(refAdd, foundAdd); } - for (int i = 0; i < kIters; ++i) + for (auto i = 0uz; i < kIters; ++i) { std::uint64_t const d = dist1(eng); // Force overflow @@ -221,7 +221,7 @@ TEST(Base58Test, multiprecision) auto const foundAdd = multiprecision_utils::toBoostMP(bigInt); EXPECT_NE(refAdd, foundAdd); } - for (int i = 0; i < kIters; ++i) + for (auto i = 0uz; i < kIters; ++i) { std::uint64_t const d = dist(eng); auto bigInt = multiprecision_utils::randomBigInt(/* minSize */ 2); @@ -239,7 +239,7 @@ TEST(Base58Test, multiprecision) auto const foundMul = multiprecision_utils::toBoostMP(bigInt); EXPECT_EQ(refMul, foundMul); } - for (int i = 0; i < kIters; ++i) + for (auto i = 0uz; i < kIters; ++i) { std::uint64_t const d = dist1(eng); // Force overflow @@ -265,7 +265,7 @@ TEST(Base58Test, fast_matches_ref) std::array b256ResultBuf[2]; std::array, 2> b256Result; - for (int i = 0; i < 2; ++i) + for (auto i = 0uz; i < 2; ++i) { std::span const outBuf{b58ResultBuf[i]}; if (i == 0) @@ -297,7 +297,7 @@ TEST(Base58Test, fast_matches_ref) } } - for (int i = 0; i < 2; ++i) + for (auto i = 0uz; i < 2; ++i) { std::span const outBuf{b256ResultBuf[i].data(), b256ResultBuf[i].size()}; if (i == 0) @@ -339,7 +339,7 @@ TEST(Base58Test, fast_matches_ref) std::array b256ResultBuf[2]; std::array, 2> b256Result; - for (int i = 0; i < 2; ++i) + for (auto i = 0uz; i < 2; ++i) { std::span const outBuf{b58ResultBuf[i].data(), b58ResultBuf[i].size()}; if (i == 0) @@ -370,7 +370,7 @@ TEST(Base58Test, fast_matches_ref) } } - for (int i = 0; i < 2; ++i) + for (auto i = 0uz; i < 2; ++i) { std::span const outBuf{b256ResultBuf[i].data(), b256ResultBuf[i].size()}; if (i == 0) @@ -425,7 +425,7 @@ TEST(Base58Test, fast_matches_ref) // test with random data constexpr std::size_t kIters = 100000; - for (int i = 0; i < kIters; ++i) + for (auto i = 0uz; i < kIters; ++i) { std::array b256DataBuf{}; auto const [tokType, b256Data] = randomB256TestData(b256DataBuf); diff --git a/src/tests/libxrpl/basics/base_uint.cpp b/src/tests/libxrpl/basics/base_uint.cpp index 174cc33aa0..10795f4563 100644 --- a/src/tests/libxrpl/basics/base_uint.cpp +++ b/src/tests/libxrpl/basics/base_uint.cpp @@ -59,65 +59,67 @@ struct BaseUintTest : public ::testing::Test static void testComparisons() { - { - static constexpr std::array, 6> kTestArgs{ - {{"0000000000000000", "0000000000000001"}, - {"0000000000000000", "ffffffffffffffff"}, - {"1234567812345678", "2345678923456789"}, - {"8000000000000000", "8000000000000001"}, - {"aaaaaaaaaaaaaaa9", "aaaaaaaaaaaaaaaa"}, - {"fffffffffffffffe", "ffffffffffffffff"}}}; + using HexPair = std::pair; - for (auto const& arg : kTestArgs) + { + static constexpr auto kTestArgs = std::to_array({ + {"0000000000000000", "0000000000000001"}, + {"0000000000000000", "ffffffffffffffff"}, + {"1234567812345678", "2345678923456789"}, + {"8000000000000000", "8000000000000001"}, + {"aaaaaaaaaaaaaaa9", "aaaaaaaaaaaaaaaa"}, + {"fffffffffffffffe", "ffffffffffffffff"}, + }); + + for (auto const& [smallerText, largerText] : kTestArgs) { - xrpl::BaseUInt<64> const u{arg.first}, v{arg.second}; + xrpl::BaseUInt<64> const smaller{smallerText}, larger{largerText}; // For code readability, we want to use general boolean // expectations instead of specific EXPECT_LT etc. - EXPECT_TRUE(u < v); - EXPECT_TRUE(u <= v); - EXPECT_TRUE(u != v); - EXPECT_FALSE(u == v); - EXPECT_FALSE(u > v); - EXPECT_FALSE(u >= v); - EXPECT_FALSE(v < u); - EXPECT_FALSE(v <= u); - EXPECT_TRUE(v != u); - EXPECT_FALSE(v == u); - EXPECT_TRUE(v > u); - EXPECT_TRUE(v >= u); - EXPECT_TRUE(u == u); - EXPECT_TRUE(v == v); + EXPECT_TRUE(smaller < larger); + EXPECT_TRUE(smaller <= larger); + EXPECT_TRUE(smaller != larger); + EXPECT_FALSE(smaller == larger); + EXPECT_FALSE(smaller > larger); + EXPECT_FALSE(smaller >= larger); + EXPECT_FALSE(larger < smaller); + EXPECT_FALSE(larger <= smaller); + EXPECT_TRUE(larger != smaller); + EXPECT_FALSE(larger == smaller); + EXPECT_TRUE(larger > smaller); + EXPECT_TRUE(larger >= smaller); + EXPECT_TRUE(smaller == smaller); + EXPECT_TRUE(larger == larger); } } { - static constexpr std::array, 6> kTestArgs{ - { - {"000000000000000000000000", "000000000000000000000001"}, - {"000000000000000000000000", "ffffffffffffffffffffffff"}, - {"0123456789ab0123456789ab", "123456789abc123456789abc"}, - {"555555555555555555555555", "55555555555a555555555555"}, - {"aaaaaaaaaaaaaaa9aaaaaaaa", "aaaaaaaaaaaaaaaaaaaaaaaa"}, - {"fffffffffffffffffffffffe", "ffffffffffffffffffffffff"}, - }}; + static constexpr auto kTestArgs = std::to_array({ + {"000000000000000000000000", "000000000000000000000001"}, + {"000000000000000000000000", "ffffffffffffffffffffffff"}, + {"0123456789ab0123456789ab", "123456789abc123456789abc"}, + {"555555555555555555555555", "55555555555a555555555555"}, + {"aaaaaaaaaaaaaaa9aaaaaaaa", "aaaaaaaaaaaaaaaaaaaaaaaa"}, + {"fffffffffffffffffffffffe", "ffffffffffffffffffffffff"}, + }); - for (auto const& arg : kTestArgs) + for (auto const& [smallerText, largerText] : kTestArgs) { - xrpl::BaseUInt<96> const u{arg.first}, v{arg.second}; - EXPECT_TRUE(u < v); - EXPECT_TRUE(u <= v); - EXPECT_TRUE(u != v); - EXPECT_FALSE(u == v); - EXPECT_FALSE(u > v); - EXPECT_FALSE(u >= v); - EXPECT_FALSE(v < u); - EXPECT_FALSE(v <= u); - EXPECT_TRUE(v != u); - EXPECT_FALSE(v == u); - EXPECT_TRUE(v > u); - EXPECT_TRUE(v >= u); - EXPECT_TRUE(u == u); - EXPECT_TRUE(v == v); + xrpl::BaseUInt<96> const smaller{smallerText}, larger{largerText}; + EXPECT_TRUE(smaller < larger); + EXPECT_TRUE(smaller <= larger); + EXPECT_TRUE(smaller != larger); + EXPECT_FALSE(smaller == larger); + EXPECT_FALSE(smaller > larger); + EXPECT_FALSE(smaller >= larger); + EXPECT_FALSE(larger < smaller); + EXPECT_FALSE(larger <= smaller); + EXPECT_TRUE(larger != smaller); + EXPECT_FALSE(larger == smaller); + EXPECT_TRUE(larger > smaller); + EXPECT_TRUE(larger >= smaller); + EXPECT_TRUE(smaller == smaller); + EXPECT_TRUE(larger == larger); } } } @@ -401,14 +403,14 @@ TEST_F(BaseUintTest, base_uint) { } }; - constexpr StrBaseUInt kTestCases[] = { + constexpr auto kTestCases = std::to_array({ "000000000000000000000000", "000000000000000000000001", "fedcba9876543210ABCDEF91", "19FEDCBA0123456789abcdef", "800000000000000000000000", "fFfFfFfFfFfFfFfFfFfFfFfF", - }; + }); for (StrBaseUInt const& t : kTestCases) { diff --git a/src/tests/libxrpl/basics/join.cpp b/src/tests/libxrpl/basics/join.cpp index 66c832678b..427f0b42bc 100644 --- a/src/tests/libxrpl/basics/join.cpp +++ b/src/tests/libxrpl/basics/join.cpp @@ -19,11 +19,11 @@ struct JoinTest : public ::testing::Test TEST_F(JoinTest, join) { - auto test = [](auto collectionanddelimiter, std::string expected) { + auto test = [](auto collectionAndDelimiter, std::string expected) { std::stringstream ss; // Put something else in the buffer before and after to ensure that // the << operator returns the stream correctly. - ss << "(" << collectionanddelimiter << ")"; + ss << "(" << collectionAndDelimiter << ")"; auto const str = ss.str(); EXPECT_EQ(str.substr(1, str.length() - 2), expected); EXPECT_EQ(str.front(), '('); diff --git a/src/tests/libxrpl/consensus/CensorshipDetector.cpp b/src/tests/libxrpl/consensus/CensorshipDetector.cpp index aa6b2d086b..2c6b6ec731 100644 --- a/src/tests/libxrpl/consensus/CensorshipDetector.cpp +++ b/src/tests/libxrpl/consensus/CensorshipDetector.cpp @@ -69,7 +69,7 @@ TEST(CensorshipDetectorTest, censorship_detector) runRound(cdet, ++round, {23, 24, 25, 26}, {25, 27}, {23, 26}, {24}); runRound(cdet, ++round, {23, 26, 28}, {26, 28}, {23}, {}); - for (int i = 0; i != 10; ++i) + for (auto i = 0uz; i != 10; ++i) runRound(cdet, ++round, {23}, {}, {23}, {}); runRound(cdet, ++round, {23, 29}, {29}, {23}, {}); diff --git a/src/tests/libxrpl/csf/TrustGraph.h b/src/tests/libxrpl/csf/TrustGraph.h index d010b954e0..8a804fcd5b 100644 --- a/src/tests/libxrpl/csf/TrustGraph.h +++ b/src/tests/libxrpl/csf/TrustGraph.h @@ -118,9 +118,9 @@ public: std::vector res; // Loop over all pairs of uniqueUNLs - for (int i = 0; i < uniqueUNLs.size(); ++i) + for (auto i = 0uz; i < uniqueUNLs.size(); ++i) { - for (int j = (i + 1); j < uniqueUNLs.size(); ++j) + for (auto j = i + 1; j < uniqueUNLs.size(); ++j) { auto const& unlA = uniqueUNLs[i]; auto const& unlB = uniqueUNLs[j]; diff --git a/src/tests/libxrpl/csf/random.h b/src/tests/libxrpl/csf/random.h index 007bdecb1b..56838bb280 100644 --- a/src/tests/libxrpl/csf/random.h +++ b/src/tests/libxrpl/csf/random.h @@ -24,11 +24,12 @@ randomWeightedShuffle(std::vector v, std::vector w, G& g) { using std::swap; - for (int i = 0; i < v.size() - 1; ++i) + for (auto i = 0uz; i + 1 < v.size(); ++i) { - // pick a random item weighted by w - std::discrete_distribution<> dd(w.begin() + i, w.end()); // NOLINT(misc-const-correctness) - auto idx = dd(g); + // Pick a random item from the unplaced tail, weighted by w. + // NOLINTNEXTLINE(misc-const-correctness) + std::discrete_distribution dd(w.begin() + i, w.end()); + auto const idx = i + dd(g); std::swap(v[i], v[idx]); std::swap(w[i], w[idx]); } diff --git a/src/tests/libxrpl/nodestore/Database.cpp b/src/tests/libxrpl/nodestore/Database.cpp index 23087a2f84..82012ed347 100644 --- a/src/tests/libxrpl/nodestore/Database.cpp +++ b/src/tests/libxrpl/nodestore/Database.cpp @@ -27,11 +27,11 @@ namespace { std::vector allBackends() { - std::vector types{"memory", "nudb"}; #if XRPL_ROCKSDB_AVAILABLE - types.emplace_back("rocksdb"); + return {"memory", "nudb", "rocksdb"}; +#else + return {"memory", "nudb"}; #endif - return types; } std::vector diff --git a/src/tests/libxrpl/resource/Logic.cpp b/src/tests/libxrpl/resource/Logic.cpp index 1f935ebf4b..a3362b4540 100644 --- a/src/tests/libxrpl/resource/Logic.cpp +++ b/src/tests/libxrpl/resource/Logic.cpp @@ -17,9 +17,12 @@ #include #include +#include #include #include +#include #include +#include namespace xrpl::Resource { @@ -54,9 +57,10 @@ protected: //-------------------------------------------------------------------------- - static void - populateGossip(Gossip& gossip) + static Gossip + makeGossip() { + Gossip gossip; std::uint8_t const v(10 + randInt(9)); std::uint8_t const n(10 + randInt(9)); gossip.items.reserve(n); @@ -71,8 +75,9 @@ protected: static_cast(v + i), }}; item.address = beast::IP::Endpoint{beast::IP::AddressV4{d}}; - gossip.items.push_back(item); + gossip.items.push_back(std::move(item)); } + return gossip; } }; @@ -87,8 +92,8 @@ TEST_F(ResourceManagerTest, limited_warn_drop) Consumer c{logic.newInboundEndpoint(addr)}; // Create load until we get a warning - int n = 10000; - bool warned = false; + auto n = 10000; + auto warned = false; while (--n >= 0) { @@ -97,7 +102,7 @@ TEST_F(ResourceManagerTest, limited_warn_drop) warned = true; break; } - ++logic.clock(); + logic.advance(); } ASSERT_TRUE(warned) << "Loop count exceeded without warning"; @@ -113,7 +118,7 @@ TEST_F(ResourceManagerTest, limited_warn_drop) EXPECT_TRUE(c.disconnect(j_)); break; } - ++logic.clock(); + logic.advance(); } ASSERT_TRUE(dropped) << "Loop count exceeded without dropping"; @@ -135,7 +140,7 @@ TEST_F(ResourceManagerTest, limited_warn_drop) auto n = kSecondsUntilExpiration + 1s; while (--n > 0s) { - ++logic.clock(); + logic.advance(); logic.periodicActivity(); Consumer const c{logic.newInboundEndpoint(addr)}; if (c.disposition() != Disposition::Drop) @@ -167,7 +172,7 @@ TEST_F(ResourceManagerTest, unlimited_warn_drop) warned = true; break; } - ++logic.clock(); + logic.advance(); } EXPECT_FALSE(warned) << "Should loop forever with no warning"; @@ -175,6 +180,8 @@ TEST_F(ResourceManagerTest, unlimited_warn_drop) TEST_F(ResourceManagerTest, charges) { + static constexpr auto kDecayTicks = 128uz; + TestLogic logic{j_}; { @@ -183,7 +190,7 @@ TEST_F(ResourceManagerTest, charges) Charge const fee{1000}; JLOG(j_.info()) << "Charging " << c.toString() << " " << fee << " per second"; c.charge(fee); - for (int i = 0; i < 128; ++i) + for (auto tick = 0uz; tick < kDecayTicks; ++tick) { JLOG(j_.info()) << "Time= " << logic.clock().now().time_since_epoch().count() << ", Balance = " << c.balance(); @@ -196,7 +203,7 @@ TEST_F(ResourceManagerTest, charges) Consumer c{logic.newInboundEndpoint(address)}; Charge const fee{1000}; JLOG(j_.info()) << "Charging " << c.toString() << " " << fee << " per second"; - for (int i = 0; i < 128; ++i) + for (auto tick = 0uz; tick < kDecayTicks; ++tick) { c.charge(fee); JLOG(j_.info()) << "Time= " << logic.clock().now().time_since_epoch().count() @@ -210,13 +217,10 @@ TEST_F(ResourceManagerTest, imports) { TestLogic logic{j_}; - Gossip g[5]; - - for (auto& i : g) - populateGossip(i); - - for (int i = 0; i < 5; ++i) - logic.importConsumers(std::to_string(i), g[i]); + static constexpr auto kGossipSources = 5uz; + std::ranges::for_each(std::views::iota(0uz, kGossipSources), [&](auto const i) { + logic.importConsumers(std::to_string(i), makeGossip()); + }); } TEST_F(ResourceManagerTest, import) @@ -233,7 +237,7 @@ TEST_F(ResourceManagerTest, import) 1, }}; item.address = beast::IP::Endpoint{beast::IP::AddressV4{d}}; - g.items.push_back(item); + g.items.push_back(std::move(item)); logic.importConsumers("g", g); } diff --git a/src/tests/libxrpl/shamap/SHAMap.cpp b/src/tests/libxrpl/shamap/SHAMap.cpp index 238c34bf9c..e662e16be4 100644 --- a/src/tests/libxrpl/shamap/SHAMap.cpp +++ b/src/tests/libxrpl/shamap/SHAMap.cpp @@ -257,12 +257,9 @@ TEST_P(SHAMapTest, add_traverse_snapshot_build_tear_and_iterate) map.invariants(); } - int h = 7; + auto keyIndex = kKeys.size(); for (auto const& k : map) - { - EXPECT_EQ(k.key(), kKeys[h]); - --h; - } + EXPECT_EQ(k.key(), kKeys[--keyIndex]); } } @@ -288,7 +285,11 @@ TEST_F(SHAMapPathProof, verify_proof_path) uint256 rootHash; std::vector goodPath; - for (unsigned char c = 1; c < 100; ++c) + static constexpr unsigned char kFirstKey = 1; + static constexpr unsigned char kKeyCount = 100; + static constexpr unsigned char kLastKey = kKeyCount - 1; + + for (unsigned char c = kFirstKey; c < kKeyCount; ++c) { uint256 k(c); map.addItem(SHAMapNodeType::TnAccountState, makeShamapitem(k, Slice{k.data(), k.size()})); @@ -304,7 +305,7 @@ TEST_F(SHAMapPathProof, verify_proof_path) auto& proofPath = *path; EXPECT_TRUE(map.verifyProofPath(root, k, proofPath)); - if (c == 1) + if (c == kFirstKey) { // extra node proofPath.insert(proofPath.begin(), proofPath.front()); @@ -313,7 +314,7 @@ TEST_F(SHAMapPathProof, verify_proof_path) uint256 const wrongKey(c + 1); EXPECT_FALSE(map.getProofPath(wrongKey)); } - if (c == 99) + if (c == kLastKey) { key = k; rootHash = root; diff --git a/src/tests/libxrpl/shamap/SHAMapSync.cpp b/src/tests/libxrpl/shamap/SHAMapSync.cpp index 400509d217..e4bcbd8970 100644 --- a/src/tests/libxrpl/shamap/SHAMapSync.cpp +++ b/src/tests/libxrpl/shamap/SHAMapSync.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -33,15 +34,17 @@ protected: boost::intrusive_ptr makeRandomAS() { + static constexpr auto kWordsPerState = 3uz; + Serializer s; - for (int d = 0; d < 3; ++d) + for (auto word = 0uz; word < kWordsPerState; ++word) s.add32(randInt(eng_)); return makeShamapitem(s.getSHA512Half(), s.slice()); } bool - confuseMap(SHAMap& map, int count) + confuseMap(SHAMap& map, std::size_t count) { // add a bunch of random states to a map, then remove them // map should be the same @@ -49,7 +52,7 @@ protected: std::list items; - for (int i = 0; i < count; ++i) + for (auto i = 0uz; i < count; ++i) { auto item = makeRandomAS(); items.push_back(item->key()); @@ -86,26 +89,30 @@ TEST_F(SHAMapSyncTest, sync) SHAMap source{SHAMapType::FREE, f}; SHAMap destination{SHAMapType::FREE, f2}; - int const items = 10000; - for (int i = 0; i < items; ++i) + static constexpr auto kItemCount = 10000uz; + static constexpr auto kInvariantInterval = 100uz; + static constexpr auto kNodesToConfuse = 500uz; + static constexpr auto kMaxNodesPerRequest = 2048; + + for (auto i = 0uz; i < kItemCount; ++i) { source.addItem(SHAMapNodeType::TnAccountState, makeRandomAS()); - if (i % 100 == 0) + if (i % kInvariantInterval == 0) source.invariants(); } source.invariants(); - ASSERT_TRUE(confuseMap(source, 500)); + ASSERT_TRUE(confuseMap(source, kNodesToConfuse)); source.invariants(); source.setImmutable(); - int count = 0; + std::size_t count = 0; source.visitLeaves([&count]([[maybe_unused]] auto const& item) { ++count; }); - EXPECT_EQ(count, items); + EXPECT_EQ(count, kItemCount); std::vector missingNodes; - source.walkMap(missingNodes, 2048); + source.walkMap(missingNodes, kMaxNodesPerRequest); EXPECT_TRUE(missingNodes.empty()); destination.setSynching(); @@ -128,7 +135,7 @@ TEST_F(SHAMapSyncTest, sync) f.clock().advance(std::chrono::seconds(1)); // get the list of nodes we know we need - auto nodesMissing = destination.getMissingNodes(2048, nullptr); + auto nodesMissing = destination.getMissingNodes(kMaxNodesPerRequest, nullptr); if (nodesMissing.empty()) break; From 97f35add2e53a04a30fb5b344d89da50b65073af Mon Sep 17 00:00:00 2001 From: Braedon Klock Date: Mon, 3 Aug 2026 17:08:42 -0400 Subject: [PATCH 08/32] fix: Add null check for account object reads (#7717) Co-authored-by: Mayukha Vadari --- src/xrpld/rpc/handlers/account/AccountObjects.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/xrpld/rpc/handlers/account/AccountObjects.cpp b/src/xrpld/rpc/handlers/account/AccountObjects.cpp index 4a34ff02fc..ee2595bf94 100644 --- a/src/xrpld/rpc/handlers/account/AccountObjects.cpp +++ b/src/xrpld/rpc/handlers/account/AccountObjects.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -191,6 +192,13 @@ getAccountObjects( for (; entryIter != dirEntries.end(); ++entryIter) { auto const sleNode = ledger.read(keylet::child(*entryIter)); + if (!sleNode) + { + // LCOV_EXCL_START + UNREACHABLE("xrpl::doAccountObjects : null SLE"); + continue; + // LCOV_EXCL_STOP + } bool canAppend = true; From 765babb20dacfadf70c534b4650c539b65f8f3b7 Mon Sep 17 00:00:00 2001 From: Timur Yalymov <36795566+tyalymov@users.noreply.github.com> Date: Mon, 3 Aug 2026 22:11:33 +0100 Subject: [PATCH 09/32] fix: Add VaultInvariant check that lossUnrealized is non-negative (#7863) Co-authored-by: Vito Tumas <5780819+Tapanito@users.noreply.github.com> --- src/libxrpl/tx/invariants/VaultInvariant.cpp | 6 ++++ src/test/app/Invariants_test.cpp | 35 ++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/src/libxrpl/tx/invariants/VaultInvariant.cpp b/src/libxrpl/tx/invariants/VaultInvariant.cpp index a9ba0ec874..eca50eb809 100644 --- a/src/libxrpl/tx/invariants/VaultInvariant.cpp +++ b/src/libxrpl/tx/invariants/VaultInvariant.cpp @@ -483,6 +483,12 @@ ValidVault::finalize( result = false; } + if (view.rules().enabled(fixCleanup3_4_0) && afterVault.lossUnrealized < kZero) + { + JLOG(j.fatal()) << "Invariant failed: loss unrealized must not be negative"; + result = false; + } + if (afterVault.assetsTotal < kZero) { JLOG(j.fatal()) << "Invariant failed: assets outstanding must be positive"; diff --git a/src/test/app/Invariants_test.cpp b/src/test/app/Invariants_test.cpp index eaf1f2704c..ac6d8e068f 100644 --- a/src/test/app/Invariants_test.cpp +++ b/src/test/app/Invariants_test.cpp @@ -3374,6 +3374,41 @@ class Invariants_test : public beast::unit_test::Suite precloseXrp, TxAccount::A2); + // A negative loss unrealized must trip the invariant. ttLOAN_MANAGE is + // allowed to change loss unrealized, so it isolates this check from the + // "must not change loss unrealized" invariant. Gated behind + // fixCleanup3_4_0 (see below). + doInvariantCheck( + {"loss unrealized must not be negative"}, + [&](Account const& a1, Account const& a2, ApplyContext& ac) { + auto const keylet = keylet::vault(a1.id(), ac.view().seq()); + return kAdjust(ac.view(), keylet, kArgs(a2.id(), 0, [&](Adjustments& sample) { + sample.lossUnrealized = -1; + })); + }, + XRPAmount{}, + STTx{ttLOAN_MANAGE, [](STObject& tx) {}}, + {tecINVARIANT_FAILED, tecINVARIANT_FAILED}, + precloseXrp, + TxAccount::A2); + + // Without fixCleanup3_4_0 the same state must NOT trip the invariant, + // preserving pre-amendment behavior (no fork risk). + doInvariantCheck( + makeEnv(defaultAmendments() - fixCleanup3_4_0), + {}, + [&](Account const& a1, Account const& a2, ApplyContext& ac) { + auto const keylet = keylet::vault(a1.id(), ac.view().seq()); + return kAdjust(ac.view(), keylet, kArgs(a2.id(), 0, [&](Adjustments& sample) { + sample.lossUnrealized = -1; + })); + }, + XRPAmount{}, + STTx{ttLOAN_MANAGE, [](STObject& tx) {}}, + {tesSUCCESS, tesSUCCESS}, + precloseXrp, + TxAccount::A2); + doInvariantCheck( {"set assets outstanding must not exceed assets maximum"}, [&](Account const& a1, Account const& a2, ApplyContext& ac) { From b8451ffa325dcd44db68c630e649c6a2ddeebe96 Mon Sep 17 00:00:00 2001 From: Luc des Trois Maisons Date: Mon, 3 Aug 2026 17:17:23 -0400 Subject: [PATCH 10/32] fix: Add missing value_type to JSON iterators (#7907) --- include/xrpl/json/json_value.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/xrpl/json/json_value.h b/include/xrpl/json/json_value.h index 47ad3ac1e0..260917face 100644 --- a/include/xrpl/json/json_value.h +++ b/include/xrpl/json/json_value.h @@ -623,6 +623,7 @@ class ValueConstIterator : public ValueIteratorBase public: using size_t = unsigned int; using difference_type = int; + using value_type = Value const; using reference = Value const&; using pointer = Value const*; using SelfType = ValueConstIterator; @@ -687,6 +688,7 @@ class ValueIterator : public ValueIteratorBase public: using size_t = unsigned int; using difference_type = int; + using value_type = Value; using reference = Value&; using pointer = Value*; using SelfType = ValueIterator; From 06488c1318d96f56d0536251bee08ac85fa7fdd3 Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Tue, 4 Aug 2026 14:46:55 +0100 Subject: [PATCH 11/32] chore: Rename CamelCase namespaces to snake_case (#7933) Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .clang-tidy | 2 + include/xrpl/basics/Resolver.h | 2 +- include/xrpl/beast/insight/StatsDCollector.h | 2 +- include/xrpl/beast/net/IPAddress.h | 10 +- include/xrpl/beast/net/IPAddressConversion.h | 20 +- include/xrpl/beast/net/IPAddressV4.h | 4 +- include/xrpl/beast/net/IPAddressV6.h | 4 +- include/xrpl/beast/net/IPEndpoint.h | 12 +- include/xrpl/core/ServiceRegistry.h | 6 +- include/xrpl/ledger/helpers/LendingHelpers.h | 12 +- include/xrpl/net/AutoSocket.h | 8 +- include/xrpl/peerfinder/Config.h | 8 +- include/xrpl/peerfinder/PeerfinderManager.h | 20 +- include/xrpl/peerfinder/Slot.h | 8 +- include/xrpl/peerfinder/Types.h | 10 +- include/xrpl/peerfinder/detail/Bootcache.h | 20 +- include/xrpl/peerfinder/detail/Checker.h | 8 +- include/xrpl/peerfinder/detail/Counts.h | 16 +- include/xrpl/peerfinder/detail/Fixed.h | 8 +- include/xrpl/peerfinder/detail/Handouts.h | 28 +-- include/xrpl/peerfinder/detail/Livecache.h | 28 +-- include/xrpl/peerfinder/detail/Logic.h | 90 +++---- include/xrpl/peerfinder/detail/SlotImp.h | 28 +-- include/xrpl/peerfinder/detail/Source.h | 4 +- .../xrpl/peerfinder/detail/SourceStrings.h | 4 +- include/xrpl/peerfinder/detail/Store.h | 8 +- include/xrpl/peerfinder/detail/Tuning.h | 4 +- include/xrpl/peerfinder/make_Manager.h | 4 +- include/xrpl/protocol/ApiVersion.h | 28 +-- include/xrpl/protocol/BuildInfo.h | 4 +- include/xrpl/protocol/ErrorCodes.h | 4 +- include/xrpl/protocol/MultiApiJson.h | 2 +- .../xrpl/protocol/NFTSyntheticSerializer.h | 4 +- include/xrpl/protocol/Protocol.h | 4 +- include/xrpl/protocol/PublicKey.h | 2 +- include/xrpl/protocol/XChainAttestations.h | 8 +- include/xrpl/resource/Charge.h | 6 +- include/xrpl/resource/Consumer.h | 4 +- include/xrpl/resource/Disposition.h | 4 +- include/xrpl/resource/Fees.h | 4 +- include/xrpl/resource/Gossip.h | 6 +- include/xrpl/resource/README.md | 6 +- include/xrpl/resource/ResourceManager.h | 12 +- include/xrpl/resource/detail/Entry.h | 4 +- include/xrpl/resource/detail/Import.h | 4 +- include/xrpl/resource/detail/Key.h | 8 +- include/xrpl/resource/detail/Kind.h | 4 +- include/xrpl/resource/detail/Logic.h | 16 +- include/xrpl/resource/detail/Tuning.h | 4 +- include/xrpl/server/InfoSub.h | 2 +- include/xrpl/server/Session.h | 2 +- include/xrpl/server/detail/BaseHTTPPeer.h | 2 +- include/xrpl/server/detail/BaseWSPeer.h | 2 +- src/libxrpl/basics/ResolverAsio.cpp | 4 +- src/libxrpl/basics/StringUtilities.cpp | 2 +- src/libxrpl/beast/insight/StatsDCollector.cpp | 8 +- src/libxrpl/beast/net/IPAddressConversion.cpp | 4 +- src/libxrpl/beast/net/IPAddressV4.cpp | 4 +- src/libxrpl/beast/net/IPAddressV6.cpp | 4 +- src/libxrpl/beast/net/IPEndpoint.cpp | 4 +- src/libxrpl/ledger/helpers/LendingHelpers.cpp | 24 +- src/libxrpl/peerfinder/Bootcache.cpp | 26 +- src/libxrpl/peerfinder/Config.cpp | 14 +- src/libxrpl/peerfinder/Endpoint.cpp | 8 +- src/libxrpl/peerfinder/PeerfinderManager.cpp | 16 +- src/libxrpl/peerfinder/SlotImp.cpp | 28 +-- src/libxrpl/peerfinder/SourceStrings.cpp | 8 +- src/libxrpl/protocol/BuildInfo.cpp | 4 +- src/libxrpl/protocol/ErrorCodes.cpp | 6 +- .../protocol/NFTSyntheticSerializer.cpp | 4 +- src/libxrpl/protocol/RPCErr.cpp | 2 +- src/libxrpl/protocol/STParsedJSON.cpp | 34 +-- src/libxrpl/protocol/XChainAttestations.cpp | 4 +- src/libxrpl/resource/Charge.cpp | 4 +- src/libxrpl/resource/Consumer.cpp | 12 +- src/libxrpl/resource/Fees.cpp | 6 +- src/libxrpl/resource/ResourceManager.cpp | 14 +- src/libxrpl/server/InfoSub.cpp | 2 +- src/libxrpl/server/JSONRPCUtil.cpp | 4 +- src/libxrpl/server/Port.cpp | 2 +- .../tx/transactors/bridge/XChainBridge.cpp | 28 +-- .../tx/transactors/lending/LoanBrokerSet.cpp | 2 +- .../tx/transactors/lending/LoanPay.cpp | 2 +- .../tx/transactors/lending/LoanSet.cpp | 2 +- src/test/app/Batch_test.cpp | 2 +- src/test/app/Delegate_test.cpp | 10 +- src/test/app/FixNFTokenPageLinks_test.cpp | 40 ++-- src/test/app/Invariants_test.cpp | 4 +- src/test/app/LedgerReplay_test.cpp | 6 +- src/test/app/LendingHelpers_test.cpp | 42 ++-- src/test/app/LoanBroker_test.cpp | 60 ++--- src/test/app/Loan_test.cpp | 54 ++--- src/test/app/PathMPT_test.cpp | 32 +-- src/test/app/Path_test.cpp | 40 ++-- src/test/app/PermissionedDEX_test.cpp | 16 +- src/test/app/SHAMapStore_test.cpp | 14 +- src/test/app/Sponsor_test.cpp | 14 +- src/test/app/TxQ_test.cpp | 10 +- src/test/app/Vault_test.cpp | 8 +- src/test/basics/PerfLog_test.cpp | 8 +- src/test/beast/IPEndpointCommon.h | 4 +- src/test/beast/IPEndpoint_test.cpp | 4 +- src/test/jtx/AMM.h | 2 +- src/test/jtx/Env.h | 2 +- src/test/jtx/TestHelpers.h | 4 +- src/test/jtx/impl/AMM.cpp | 2 +- src/test/jtx/impl/Env.cpp | 4 +- src/test/jtx/impl/TestHelpers.cpp | 14 +- src/test/jtx/impl/attester.cpp | 4 +- src/test/jtx/impl/ledgerStateFixes.cpp | 4 +- src/test/jtx/ledgerStateFix.h | 4 +- src/test/jtx/rpc.h | 2 +- src/test/overlay/TMGetObjectByHash_test.cpp | 12 +- src/test/overlay/compression_test.cpp | 2 +- src/test/overlay/reduce_relay_test.cpp | 6 +- src/test/overlay/tx_reduce_relay_test.cpp | 8 +- src/test/protocol/BuildInfo_test.cpp | 32 +-- src/test/protocol/InnerObjectFormats_test.cpp | 10 +- src/test/protocol/MultiApiJson_test.cpp | 28 +-- src/test/rpc/AccountLines_test.cpp | 36 +-- src/test/rpc/AccountTx_test.cpp | 22 +- src/test/rpc/Book_test.cpp | 8 +- src/test/rpc/Handler_test.cpp | 4 +- src/test/rpc/JSONRPC_test.cpp | 48 ++-- src/test/rpc/KeyGeneration_test.cpp | 4 +- src/test/rpc/LedgerEntry_test.cpp | 22 +- src/test/rpc/LedgerRPC_test.cpp | 2 +- src/test/rpc/LedgerRequest_test.cpp | 26 +- src/test/rpc/NoRippleCheck_test.cpp | 6 +- src/test/rpc/RPCCall_test.cpp | 8 +- src/test/rpc/RPCHelpers_test.cpp | 32 +-- src/test/rpc/Status_test.cpp | 6 +- src/test/rpc/TransactionEntry_test.cpp | 2 +- src/test/rpc/Transaction_test.cpp | 72 +++--- src/test/rpc/Version_test.cpp | 62 ++--- .../libxrpl/helpers/TestServiceRegistry.h | 2 +- src/tests/libxrpl/peerfinder/Livecache.cpp | 26 +- src/tests/libxrpl/peerfinder/PeerFinder.cpp | 58 ++--- src/tests/libxrpl/protocol/ApiVersion.cpp | 24 +- src/tests/libxrpl/resource/Logic.cpp | 20 +- src/xrpld/app/consensus/RCLConsensus.cpp | 2 +- src/xrpld/app/ledger/LedgerReplayer.h | 4 +- src/xrpld/app/ledger/LedgerToJson.h | 4 +- src/xrpld/app/ledger/detail/InboundLedger.cpp | 20 +- .../app/ledger/detail/InboundTransactions.cpp | 10 +- .../app/ledger/detail/LedgerDeltaAcquire.cpp | 10 +- src/xrpld/app/ledger/detail/LedgerMaster.cpp | 12 +- .../app/ledger/detail/LedgerReplayTask.cpp | 8 +- .../app/ledger/detail/LedgerReplayer.cpp | 4 +- src/xrpld/app/ledger/detail/LedgerToJson.cpp | 14 +- .../app/ledger/detail/SkipListAcquire.cpp | 10 +- src/xrpld/app/main/Application.cpp | 20 +- src/xrpld/app/main/CollectorManager.cpp | 4 +- src/xrpld/app/main/GRPCServer.cpp | 32 +-- src/xrpld/app/main/GRPCServer.h | 16 +- src/xrpld/app/main/Main.cpp | 6 +- src/xrpld/app/misc/DeliverMax.h | 4 +- src/xrpld/app/misc/NetworkOPs.cpp | 20 +- src/xrpld/app/misc/detail/DeliverMax.cpp | 4 +- src/xrpld/app/misc/detail/Transaction.cpp | 2 +- src/xrpld/app/misc/detail/WorkBase.h | 2 +- src/xrpld/app/rdb/PeerFinder.h | 2 +- src/xrpld/app/rdb/detail/PeerFinder.cpp | 8 +- src/xrpld/core/Config.h | 2 +- src/xrpld/overlay/Overlay.h | 4 +- src/xrpld/overlay/Peer.h | 8 +- src/xrpld/overlay/detail/ConnectAttempt.cpp | 6 +- src/xrpld/overlay/detail/ConnectAttempt.h | 10 +- src/xrpld/overlay/detail/Handshake.cpp | 22 +- src/xrpld/overlay/detail/Handshake.h | 12 +- src/xrpld/overlay/detail/OverlayImpl.cpp | 60 ++--- src/xrpld/overlay/detail/OverlayImpl.h | 22 +- src/xrpld/overlay/detail/PeerImp.cpp | 226 +++++++++--------- src/xrpld/overlay/detail/PeerImp.h | 55 +++-- src/xrpld/overlay/detail/Tuning.h | 6 +- src/xrpld/overlay/make_Overlay.h | 2 +- src/xrpld/peerfinder/PeerfinderManager.h | 4 +- .../peerfinder/detail/PeerfinderConfig.cpp | 4 +- src/xrpld/peerfinder/detail/StoreSqdb.h | 6 +- src/xrpld/perflog/detail/PerfLogImp.h | 2 +- src/xrpld/rpc/BookChanges.h | 4 +- src/xrpld/rpc/CTID.h | 4 +- src/xrpld/rpc/Context.h | 8 +- src/xrpld/rpc/DeliveredAmount.h | 10 +- src/xrpld/rpc/GRPCHandlers.h | 10 +- src/xrpld/rpc/MPTokenIssuanceID.h | 4 +- src/xrpld/rpc/Output.h | 4 +- src/xrpld/rpc/RPCCall.h | 4 +- src/xrpld/rpc/RPCHandler.h | 6 +- src/xrpld/rpc/Role.h | 10 +- src/xrpld/rpc/ServerHandler.h | 10 +- src/xrpld/rpc/Status.h | 17 +- src/xrpld/rpc/detail/DeliveredAmount.cpp | 12 +- src/xrpld/rpc/detail/Handler.cpp | 24 +- src/xrpld/rpc/detail/Handler.h | 8 +- src/xrpld/rpc/detail/LegacyPathFind.cpp | 8 +- src/xrpld/rpc/detail/LegacyPathFind.h | 4 +- src/xrpld/rpc/detail/MPTokenIssuanceID.cpp | 4 +- src/xrpld/rpc/detail/PathRequest.cpp | 6 +- src/xrpld/rpc/detail/PathRequest.h | 4 +- src/xrpld/rpc/detail/PathRequestManager.cpp | 4 +- src/xrpld/rpc/detail/PathRequestManager.h | 4 +- src/xrpld/rpc/detail/RPCCall.cpp | 20 +- src/xrpld/rpc/detail/RPCHandler.cpp | 14 +- src/xrpld/rpc/detail/RPCHelpers.cpp | 50 ++-- src/xrpld/rpc/detail/RPCHelpers.h | 8 +- src/xrpld/rpc/detail/RPCLedgerHelpers.cpp | 36 +-- src/xrpld/rpc/detail/RPCLedgerHelpers.h | 6 +- src/xrpld/rpc/detail/RPCSub.cpp | 12 +- src/xrpld/rpc/detail/Role.cpp | 14 +- src/xrpld/rpc/detail/ServerHandler.cpp | 76 +++--- src/xrpld/rpc/detail/Status.cpp | 8 +- src/xrpld/rpc/detail/TransactionSign.cpp | 156 ++++++------ src/xrpld/rpc/detail/TransactionSign.h | 8 +- src/xrpld/rpc/detail/Tuning.h | 4 +- src/xrpld/rpc/handlers/ChannelVerify.cpp | 4 +- src/xrpld/rpc/handlers/Handlers.h | 144 +++++------ src/xrpld/rpc/handlers/VaultInfo.cpp | 12 +- .../rpc/handlers/account/AccountChannels.cpp | 20 +- .../handlers/account/AccountCurrencies.cpp | 12 +- .../rpc/handlers/account/AccountInfo.cpp | 18 +- .../rpc/handlers/account/AccountLines.cpp | 22 +- .../rpc/handlers/account/AccountNFTs.cpp | 20 +- .../rpc/handlers/account/AccountObjects.cpp | 32 +-- .../rpc/handlers/account/AccountOffers.cpp | 28 +-- src/xrpld/rpc/handlers/account/AccountTx.cpp | 62 ++--- .../rpc/handlers/account/GatewayBalances.cpp | 14 +- .../rpc/handlers/account/NoRippleCheck.cpp | 20 +- src/xrpld/rpc/handlers/account/OwnerInfo.cpp | 4 +- src/xrpld/rpc/handlers/admin/BlackList.cpp | 2 +- src/xrpld/rpc/handlers/admin/UnlList.cpp | 2 +- .../rpc/handlers/admin/data/CanDelete.cpp | 10 +- .../rpc/handlers/admin/data/LedgerCleaner.cpp | 4 +- .../rpc/handlers/admin/data/LedgerRequest.cpp | 6 +- .../admin/keygen/ValidationCreate.cpp | 2 +- .../handlers/admin/keygen/WalletPropose.cpp | 10 +- src/xrpld/rpc/handlers/admin/log/LogLevel.cpp | 2 +- .../rpc/handlers/admin/log/LogRotate.cpp | 4 +- src/xrpld/rpc/handlers/admin/peer/Connect.cpp | 10 +- .../admin/peer/PeerReservationsAdd.cpp | 8 +- .../admin/peer/PeerReservationsDel.cpp | 6 +- .../admin/peer/PeerReservationsList.cpp | 2 +- src/xrpld/rpc/handlers/admin/peer/Peers.cpp | 2 +- .../admin/server_control/LedgerAccept.cpp | 2 +- .../handlers/admin/server_control/Stop.cpp | 8 +- .../admin/signing/ChannelAuthorize.cpp | 16 +- src/xrpld/rpc/handlers/admin/signing/Sign.cpp | 8 +- .../rpc/handlers/admin/signing/SignFor.cpp | 8 +- .../handlers/admin/status/ConsensusInfo.cpp | 2 +- .../rpc/handlers/admin/status/FetchInfo.cpp | 2 +- .../rpc/handlers/admin/status/GetCounts.cpp | 2 +- src/xrpld/rpc/handlers/admin/status/Print.cpp | 2 +- .../handlers/admin/status/ValidatorInfo.cpp | 4 +- .../admin/status/ValidatorListSites.cpp | 2 +- .../rpc/handlers/admin/status/Validators.cpp | 2 +- src/xrpld/rpc/handlers/ledger/Ledger.cpp | 10 +- src/xrpld/rpc/handlers/ledger/Ledger.h | 8 +- .../rpc/handlers/ledger/LedgerClosed.cpp | 2 +- .../rpc/handlers/ledger/LedgerCurrent.cpp | 2 +- src/xrpld/rpc/handlers/ledger/LedgerData.cpp | 20 +- src/xrpld/rpc/handlers/ledger/LedgerDiff.cpp | 6 +- src/xrpld/rpc/handlers/ledger/LedgerEntry.cpp | 173 +++++++------- .../rpc/handlers/ledger/LedgerEntryHelpers.h | 8 +- .../rpc/handlers/ledger/LedgerHeader.cpp | 4 +- src/xrpld/rpc/handlers/orderbook/AMMInfo.cpp | 6 +- .../rpc/handlers/orderbook/BookChanges.cpp | 6 +- .../rpc/handlers/orderbook/BookOffers.cpp | 44 ++-- .../handlers/orderbook/DepositAuthorized.cpp | 46 ++-- .../handlers/orderbook/GetAggregatePrice.cpp | 32 +-- .../rpc/handlers/orderbook/NFTBuyOffers.cpp | 6 +- .../rpc/handlers/orderbook/NFTOffersHelpers.h | 10 +- .../rpc/handlers/orderbook/NFTSellOffers.cpp | 6 +- src/xrpld/rpc/handlers/orderbook/PathFind.cpp | 4 +- .../rpc/handlers/orderbook/RipplePathFind.cpp | 10 +- .../rpc/handlers/server_info/Feature.cpp | 2 +- src/xrpld/rpc/handlers/server_info/Fee.cpp | 4 +- .../rpc/handlers/server_info/Manifest.cpp | 6 +- .../server_info/ServerDefinitions.cpp | 4 +- .../rpc/handlers/server_info/ServerInfo.cpp | 2 +- .../rpc/handlers/server_info/ServerState.cpp | 2 +- src/xrpld/rpc/handlers/server_info/Version.h | 8 +- .../rpc/handlers/subscribe/Subscribe.cpp | 18 +- .../rpc/handlers/subscribe/Unsubscribe.cpp | 10 +- .../rpc/handlers/transaction/Simulate.cpp | 52 ++-- src/xrpld/rpc/handlers/transaction/Submit.cpp | 14 +- .../transaction/SubmitMultiSigned.cpp | 8 +- .../handlers/transaction/TransactionEntry.cpp | 6 +- src/xrpld/rpc/handlers/transaction/Tx.cpp | 28 +-- .../rpc/handlers/transaction/TxHistory.cpp | 6 +- .../handlers/transaction/TxReduceRelay.cpp | 2 +- src/xrpld/rpc/handlers/utility/Ping.cpp | 6 +- src/xrpld/rpc/handlers/utility/Random.cpp | 6 +- tests/conan/src/example.cpp | 2 +- 293 files changed, 2104 insertions(+), 2083 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 41df5470ff..02e90d9148 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -85,6 +85,8 @@ CheckOptions: readability-braces-around-statements.ShortStatementLines: 2 readability-identifier-naming.MacroDefinitionCase: UPPER_CASE + readability-identifier-naming.NamespaceCase: lower_case + readability-identifier-naming.InlineNamespaceCase: lower_case readability-identifier-naming.ClassCase: CamelCase readability-identifier-naming.StructCase: CamelCase readability-identifier-naming.UnionCase: CamelCase diff --git a/include/xrpl/basics/Resolver.h b/include/xrpl/basics/Resolver.h index 239eb9630e..a159619517 100644 --- a/include/xrpl/basics/Resolver.h +++ b/include/xrpl/basics/Resolver.h @@ -11,7 +11,7 @@ namespace xrpl { class Resolver { public: - using HandlerType = std::function)>; + using HandlerType = std::function)>; virtual ~Resolver() = 0; diff --git a/include/xrpl/beast/insight/StatsDCollector.h b/include/xrpl/beast/insight/StatsDCollector.h index e14d3a27ff..0b44f345ba 100644 --- a/include/xrpl/beast/insight/StatsDCollector.h +++ b/include/xrpl/beast/insight/StatsDCollector.h @@ -26,7 +26,7 @@ public: * @param journal Destination for logging output. */ static std::shared_ptr - make(IP::Endpoint const& address, std::string const& prefix, Journal journal); + make(ip::Endpoint const& address, std::string const& prefix, Journal journal); }; } // namespace beast::insight diff --git a/include/xrpl/beast/net/IPAddress.h b/include/xrpl/beast/net/IPAddress.h index 4f4fb189a6..7422778ea2 100644 --- a/include/xrpl/beast/net/IPAddress.h +++ b/include/xrpl/beast/net/IPAddress.h @@ -15,7 +15,7 @@ //------------------------------------------------------------------------------ namespace beast { -namespace IP { +namespace ip { using Address = boost::asio::ip::address; @@ -73,13 +73,13 @@ isPublic(Address const& addr) return (addr.is_v4()) ? isPublic(addr.to_v4()) : isPublic(addr.to_v6()); } -} // namespace IP +} // namespace ip //------------------------------------------------------------------------------ template void -hash_append(Hasher& h, beast::IP::Address const& addr) noexcept +hash_append(Hasher& h, beast::ip::Address const& addr) noexcept { using beast::hash_append; if (addr.is_v4()) @@ -101,12 +101,12 @@ hash_append(Hasher& h, beast::IP::Address const& addr) noexcept namespace boost { template <> -struct hash<::beast::IP::Address> +struct hash<::beast::ip::Address> { explicit hash() = default; std::size_t - operator()(::beast::IP::Address const& addr) const + operator()(::beast::ip::Address const& addr) const { return ::beast::Uhash<>{}(addr); } diff --git a/include/xrpl/beast/net/IPAddressConversion.h b/include/xrpl/beast/net/IPAddressConversion.h index 73777cf841..e2d485642f 100644 --- a/include/xrpl/beast/net/IPAddressConversion.h +++ b/include/xrpl/beast/net/IPAddressConversion.h @@ -4,7 +4,7 @@ #include -namespace beast::IP { +namespace beast::ip { /** * Convert to Endpoint. @@ -32,7 +32,7 @@ toAsioAddress(Endpoint const& endpoint); boost::asio::ip::tcp::endpoint toAsioEndpoint(Endpoint const& endpoint); -} // namespace beast::IP +} // namespace beast::ip namespace beast { @@ -41,25 +41,25 @@ struct IPAddressConversion { explicit IPAddressConversion() = default; - static IP::Endpoint + static ip::Endpoint fromAsio(boost::asio::ip::address const& address) { - return IP::fromAsio(address); + return ip::fromAsio(address); } - static IP::Endpoint + static ip::Endpoint fromAsio(boost::asio::ip::tcp::endpoint const& endpoint) { - return IP::fromAsio(endpoint); + return ip::fromAsio(endpoint); } static boost::asio::ip::address - toAsioAddress(IP::Endpoint const& address) + toAsioAddress(ip::Endpoint const& address) { - return IP::toAsioAddress(address); + return ip::toAsioAddress(address); } static boost::asio::ip::tcp::endpoint - toAsioEndpoint(IP::Endpoint const& address) + toAsioEndpoint(ip::Endpoint const& address) { - return IP::toAsioEndpoint(address); + return ip::toAsioEndpoint(address); } }; diff --git a/include/xrpl/beast/net/IPAddressV4.h b/include/xrpl/beast/net/IPAddressV4.h index 94943af3ea..280c2c791c 100644 --- a/include/xrpl/beast/net/IPAddressV4.h +++ b/include/xrpl/beast/net/IPAddressV4.h @@ -2,7 +2,7 @@ #include -namespace beast::IP { +namespace beast::ip { using AddressV4 = boost::asio::ip::address_v4; @@ -25,4 +25,4 @@ isPublic(AddressV4 const& addr); char getClass(AddressV4 const& address); -} // namespace beast::IP +} // namespace beast::ip diff --git a/include/xrpl/beast/net/IPAddressV6.h b/include/xrpl/beast/net/IPAddressV6.h index b51cb62532..0659e7e405 100644 --- a/include/xrpl/beast/net/IPAddressV6.h +++ b/include/xrpl/beast/net/IPAddressV6.h @@ -2,7 +2,7 @@ #include -namespace beast::IP { +namespace beast::ip { using AddressV6 = boost::asio::ip::address_v6; @@ -18,4 +18,4 @@ isPrivate(AddressV6 const& addr); bool isPublic(AddressV6 const& addr); -} // namespace beast::IP +} // namespace beast::ip diff --git a/include/xrpl/beast/net/IPEndpoint.h b/include/xrpl/beast/net/IPEndpoint.h index c4b269e9c3..d4d3b2ab12 100644 --- a/include/xrpl/beast/net/IPEndpoint.h +++ b/include/xrpl/beast/net/IPEndpoint.h @@ -13,7 +13,7 @@ #include #include -namespace beast::IP { +namespace beast::ip { using Port = std::uint16_t; @@ -223,7 +223,7 @@ operator<<(OutputStream& os, Endpoint const& endpoint) std::istream& operator>>(std::istream& is, Endpoint& endpoint); -} // namespace beast::IP +} // namespace beast::ip //------------------------------------------------------------------------------ @@ -232,12 +232,12 @@ namespace std { * std::hash support. */ template <> -struct hash<::beast::IP::Endpoint> +struct hash<::beast::ip::Endpoint> { hash() = default; std::size_t - operator()(::beast::IP::Endpoint const& endpoint) const + operator()(::beast::ip::Endpoint const& endpoint) const { return ::beast::Uhash<>{}(endpoint); } @@ -249,12 +249,12 @@ namespace boost { * boost::hash support. */ template <> -struct hash<::beast::IP::Endpoint> +struct hash<::beast::ip::Endpoint> { hash() = default; std::size_t - operator()(::beast::IP::Endpoint const& endpoint) const + operator()(::beast::ip::Endpoint const& endpoint) const { return ::beast::Uhash<>{}(endpoint); } diff --git a/include/xrpl/core/ServiceRegistry.h b/include/xrpl/core/ServiceRegistry.h index 2747ecd9e8..000bdaa7fa 100644 --- a/include/xrpl/core/ServiceRegistry.h +++ b/include/xrpl/core/ServiceRegistry.h @@ -18,9 +18,9 @@ namespace xrpl { namespace node_store { class Database; } // namespace node_store -namespace Resource { +namespace resource { class Manager; -} // namespace Resource +} // namespace resource namespace perf { class PerfLog; } // namespace perf @@ -160,7 +160,7 @@ public: virtual PeerReservationTable& getPeerReservations() = 0; - virtual Resource::Manager& + virtual resource::Manager& getResourceManager() = 0; // Storage services diff --git a/include/xrpl/ledger/helpers/LendingHelpers.h b/include/xrpl/ledger/helpers/LendingHelpers.h index fef18e3e09..c69efff964 100644 --- a/include/xrpl/ledger/helpers/LendingHelpers.h +++ b/include/xrpl/ledger/helpers/LendingHelpers.h @@ -296,7 +296,7 @@ struct AccountingDeltas // Whole-life (pre-LendingProtocolV1_1) recognition model: interest is // recognized into AssetsTotal/DebtTotal up front, at origination. -namespace Accrual { +namespace accrual { // LoanSet origination: what's added to Vault.AssetsTotal and LoanBroker.DebtTotal AccountingDeltas @@ -318,11 +318,11 @@ loanVaultExposure(SLE::const_ref loanSle); AccountingDeltas loanPaymentDeltas(LoanPaymentParts const& parts); -} // namespace Accrual +} // namespace accrual // Cash-basis (LendingProtocolV1_1) recognition model: AssetsTotal/DebtTotal // are principal-only, interest is recognized only as it's actually paid. -namespace CashBasis { +namespace cash_basis { AccountingDeltas loanOriginationDeltas(Number const& principalRequested); @@ -333,11 +333,11 @@ loanVaultExposure(SLE::const_ref loanSle); AccountingDeltas loanPaymentDeltas(LoanPaymentParts const& parts); -} // namespace CashBasis +} // namespace cash_basis -// Public dispatchers: pick CashBasis:: if featureLendingProtocolV1_1 is +// Public dispatchers: pick cash_basis:: if featureLendingProtocolV1_1 is // enabled AND the Vault's LEVersion (VaultHelpers::getVaultVersion) is -// VaultVersion::CashBasis, else Accrual::. These are the only entry points +// VaultVersion::CashBasis, else accrual::. These are the only entry points // transactors call. AccountingDeltas loanOriginationDeltas( diff --git a/include/xrpl/net/AutoSocket.h b/include/xrpl/net/AutoSocket.h index b98885959d..d090247388 100644 --- a/include/xrpl/net/AutoSocket.h +++ b/include/xrpl/net/AutoSocket.h @@ -67,16 +67,16 @@ public: return socket_->next_layer(); } - beast::IP::Endpoint + beast::ip::Endpoint localEndpoint() { - return beast::IP::fromAsio(lowestLayer().local_endpoint()); + return beast::ip::fromAsio(lowestLayer().local_endpoint()); } - beast::IP::Endpoint + beast::ip::Endpoint remoteEndpoint() { - return beast::IP::fromAsio(lowestLayer().remote_endpoint()); + return beast::ip::fromAsio(lowestLayer().remote_endpoint()); } lowest_layer_type& diff --git a/include/xrpl/peerfinder/Config.h b/include/xrpl/peerfinder/Config.h index 9ff0d342c3..3326ae8a97 100644 --- a/include/xrpl/peerfinder/Config.h +++ b/include/xrpl/peerfinder/Config.h @@ -9,7 +9,7 @@ #include #include -namespace xrpl::PeerFinder { +namespace xrpl::peer_finder { struct PeerLimitConfig { @@ -28,7 +28,7 @@ struct Config * This includes both inbound and outbound, but does not include * fixed peers. */ - std::size_t maxPeers{Tuning::kDefaultMaxPeers}; + std::size_t maxPeers{tuning::kDefaultMaxPeers}; /** * The number of automatic outbound connections to maintain. @@ -100,7 +100,7 @@ struct Config onWrite(beast::PropertyStream::Map& map) const; /** - * Make PeerFinder::Config from peer limit and server mode parameters. + * Make peer_finder::Config from peer limit and server mode parameters. */ static Config makeConfig( @@ -160,4 +160,4 @@ to_string(Result result) noexcept return "unknown"; } -} // namespace xrpl::PeerFinder +} // namespace xrpl::peer_finder diff --git a/include/xrpl/peerfinder/PeerfinderManager.h b/include/xrpl/peerfinder/PeerfinderManager.h index ed683520c1..bb03d85537 100644 --- a/include/xrpl/peerfinder/PeerfinderManager.h +++ b/include/xrpl/peerfinder/PeerfinderManager.h @@ -15,7 +15,7 @@ #include #include -namespace xrpl::PeerFinder { +namespace xrpl::peer_finder { /** * Maintains a set of IP addresses used for getting into the network. @@ -68,17 +68,17 @@ public: * file, along with the set of corresponding IP addresses. */ virtual void - addFixedPeer(std::string_view name, std::vector const& addresses) = 0; + addFixedPeer(std::string_view name, std::vector const& addresses) = 0; /** - * Add a set of strings as fallback IP::Endpoint sources. + * Add a set of strings as fallback ip::Endpoint sources. * @param name A label used for diagnostics. */ virtual void addFallbackStrings(std::string const& name, std::vector const& strings) = 0; /** - * Add a URL as a fallback location to obtain IP::Endpoint sources. + * Add a URL as a fallback location to obtain ip::Endpoint sources. * @param name A label used for diagnostics. */ /* VFALCO NOTE Unimplemented @@ -95,8 +95,8 @@ public: */ virtual std::pair, Result> newInboundSlot( - beast::IP::Endpoint const& localEndpoint, - beast::IP::Endpoint const& remoteEndpoint) = 0; + beast::ip::Endpoint const& localEndpoint, + beast::ip::Endpoint const& remoteEndpoint) = 0; /** * Create a new outbound slot with the specified remote endpoint. @@ -104,7 +104,7 @@ public: * Usually this is because of a duplicate connection. */ virtual std::pair, Result> - newOutboundSlot(beast::IP::Endpoint const& remoteEndpoint) = 0; + newOutboundSlot(beast::ip::Endpoint const& remoteEndpoint) = 0; /** * Called when mtENDPOINTS is received. @@ -145,7 +145,7 @@ public: * @return `true` if the connection should be kept */ virtual bool - onConnected(std::shared_ptr const& slot, beast::IP::Endpoint const& localEndpoint) = 0; + onConnected(std::shared_ptr const& slot, beast::ip::Endpoint const& localEndpoint) = 0; /** * Request an active slot type. @@ -162,7 +162,7 @@ public: /** * Return a set of addresses we should connect to. */ - virtual std::vector + virtual std::vector autoconnect() = 0; virtual std::vector, std::vector>> @@ -176,4 +176,4 @@ public: oncePerSecond() = 0; }; -} // namespace xrpl::PeerFinder +} // namespace xrpl::peer_finder diff --git a/include/xrpl/peerfinder/Slot.h b/include/xrpl/peerfinder/Slot.h index 9db39ac94c..58e094afc4 100644 --- a/include/xrpl/peerfinder/Slot.h +++ b/include/xrpl/peerfinder/Slot.h @@ -7,7 +7,7 @@ #include #include -namespace xrpl::PeerFinder { +namespace xrpl::peer_finder { /** * Properties and state associated with a peer to peer overlay connection. @@ -52,13 +52,13 @@ public: /** * The remote endpoint of socket. */ - [[nodiscard]] virtual beast::IP::Endpoint const& + [[nodiscard]] virtual beast::ip::Endpoint const& remoteEndpoint() const = 0; /** * The local endpoint of the socket, when known. */ - [[nodiscard]] virtual std::optional const& + [[nodiscard]] virtual std::optional const& localEndpoint() const = 0; [[nodiscard]] virtual std::optional @@ -72,4 +72,4 @@ public: publicKey() const = 0; }; -} // namespace xrpl::PeerFinder +} // namespace xrpl::peer_finder diff --git a/include/xrpl/peerfinder/Types.h b/include/xrpl/peerfinder/Types.h index 9e82d9d65c..1327f2564f 100644 --- a/include/xrpl/peerfinder/Types.h +++ b/include/xrpl/peerfinder/Types.h @@ -8,14 +8,14 @@ #include #include -namespace xrpl::PeerFinder { +namespace xrpl::peer_finder { using clock_type = beast::AbstractClock; /** * Represents a set of addresses. */ -using IPAddresses = std::vector; +using IPAddresses = std::vector; //------------------------------------------------------------------------------ @@ -26,10 +26,10 @@ struct Endpoint { Endpoint() = default; - Endpoint(beast::IP::Endpoint ep, std::uint32_t hops); + Endpoint(beast::ip::Endpoint ep, std::uint32_t hops); std::uint32_t hops = 0; - beast::IP::Endpoint address; + beast::ip::Endpoint address; }; inline bool @@ -43,4 +43,4 @@ operator<(Endpoint const& lhs, Endpoint const& rhs) */ using Endpoints = std::vector; -} // namespace xrpl::PeerFinder +} // namespace xrpl::peer_finder diff --git a/include/xrpl/peerfinder/detail/Bootcache.h b/include/xrpl/peerfinder/detail/Bootcache.h index 2141a374fa..453d9c22d2 100644 --- a/include/xrpl/peerfinder/detail/Bootcache.h +++ b/include/xrpl/peerfinder/detail/Bootcache.h @@ -14,7 +14,7 @@ #include -namespace xrpl::PeerFinder { +namespace xrpl::peer_finder { /** * Stores IP addresses useful for gaining initial connections. @@ -65,7 +65,7 @@ private: }; using left_t = boost::bimaps:: - unordered_set_of, std::equal_to<>>; + unordered_set_of, std::equal_to<>>; using right_t = boost::bimaps::multiset_of>; using map_type = boost::bimap; using value_type = map_type::value_type; @@ -73,11 +73,11 @@ private: struct Transform { using first_argument_type = map_type::right_map::const_iterator::value_type const&; - using result_type = beast::IP::Endpoint const&; + using result_type = beast::ip::Endpoint const&; explicit Transform() = default; - beast::IP::Endpoint const& + beast::ip::Endpoint const& operator()(map_type::right_map::const_iterator::value_type const& v) const { return v.get_left(); @@ -121,7 +121,7 @@ public: size() const; /** - * IP::Endpoint iterators that traverse in decreasing valence. + * ip::Endpoint iterators that traverse in decreasing valence. */ /** @{ */ [[nodiscard]] const_iterator @@ -146,25 +146,25 @@ public: * Add a newly-learned address to the cache. */ bool - insert(beast::IP::Endpoint const& endpoint); + insert(beast::ip::Endpoint const& endpoint); /** * Add a staticallyconfigured address to the cache. */ bool - insertStatic(beast::IP::Endpoint const& endpoint); + insertStatic(beast::ip::Endpoint const& endpoint); /** * Called when an outbound connection handshake completes. */ void - onSuccess(beast::IP::Endpoint const& endpoint); + onSuccess(beast::ip::Endpoint const& endpoint); /** * Called when an outbound connection attempt fails to handshake. */ void - onFailure(beast::IP::Endpoint const& endpoint); + onFailure(beast::ip::Endpoint const& endpoint); /** * Stores the cache in the persistent database on a timer. @@ -189,4 +189,4 @@ private: flagForUpdate(); }; -} // namespace xrpl::PeerFinder +} // namespace xrpl::peer_finder diff --git a/include/xrpl/peerfinder/detail/Checker.h b/include/xrpl/peerfinder/detail/Checker.h index 28ec83adb1..e1ac1d44e0 100644 --- a/include/xrpl/peerfinder/detail/Checker.h +++ b/include/xrpl/peerfinder/detail/Checker.h @@ -11,7 +11,7 @@ #include #include -namespace xrpl::PeerFinder { +namespace xrpl::peer_finder { /** * Tests remote listening sockets to make sure they are connectable. @@ -104,7 +104,7 @@ public: */ template void - asyncConnect(beast::IP::Endpoint const& endpoint, Handler&& handler); + asyncConnect(beast::ip::Endpoint const& endpoint, Handler&& handler); private: void @@ -179,7 +179,7 @@ Checker::wait() template template void -Checker::asyncConnect(beast::IP::Endpoint const& endpoint, Handler&& handler) +Checker::asyncConnect(beast::ip::Endpoint const& endpoint, Handler&& handler) { auto const op = std::make_shared>(*this, ioContext_, std::forward(handler)); @@ -202,4 +202,4 @@ Checker::remove(BasicAsyncOp& op) cond_.notify_all(); } -} // namespace xrpl::PeerFinder +} // namespace xrpl::peer_finder diff --git a/include/xrpl/peerfinder/detail/Counts.h b/include/xrpl/peerfinder/detail/Counts.h index ce78eadce4..035103463e 100644 --- a/include/xrpl/peerfinder/detail/Counts.h +++ b/include/xrpl/peerfinder/detail/Counts.h @@ -10,7 +10,7 @@ #include #include -namespace xrpl::PeerFinder { +namespace xrpl::peer_finder { /** * Direction of a slot count adjustment. @@ -50,7 +50,7 @@ public: // Must be handshaked and in the right state XRPL_ASSERT( s.state() == Slot::State::Connected || s.state() == Slot::State::Accept, - "xrpl::PeerFinder::Counts::can_activate : valid input state"); + "xrpl::peer_finder::Counts::can_activate : valid input state"); if (s.fixed() || s.reserved()) return true; @@ -67,9 +67,9 @@ public: [[nodiscard]] std::size_t attemptsNeeded() const { - if (attempts_ >= Tuning::kMaxConnectAttempts) + if (attempts_ >= tuning::kMaxConnectAttempts) return 0; - return Tuning::kMaxConnectAttempts - attempts_; + return tuning::kMaxConnectAttempts - attempts_; } /** @@ -295,7 +295,7 @@ private: switch (s.state()) { case Slot::State::Accept: - XRPL_ASSERT(s.inbound(), "xrpl::PeerFinder::Counts::adjust : input is inbound"); + XRPL_ASSERT(s.inbound(), "xrpl::peer_finder::Counts::adjust : input is inbound"); acceptCount_ += n; break; @@ -303,7 +303,7 @@ private: case Slot::State::Connected: XRPL_ASSERT( !s.inbound(), - "xrpl::PeerFinder::Counts::adjust : input is not " + "xrpl::peer_finder::Counts::adjust : input is not " "inbound"); attempts_ += n; break; @@ -331,7 +331,7 @@ private: // LCOV_EXCL_START default: - UNREACHABLE("xrpl::PeerFinder::Counts::adjust : invalid input state"); + UNREACHABLE("xrpl::peer_finder::Counts::adjust : invalid input state"); break; // LCOV_EXCL_STOP }; @@ -391,4 +391,4 @@ private: int closingCount_{0}; }; -} // namespace xrpl::PeerFinder +} // namespace xrpl::peer_finder diff --git a/include/xrpl/peerfinder/detail/Fixed.h b/include/xrpl/peerfinder/detail/Fixed.h index 6754ec6dbd..5a52fd7c3e 100644 --- a/include/xrpl/peerfinder/detail/Fixed.h +++ b/include/xrpl/peerfinder/detail/Fixed.h @@ -7,7 +7,7 @@ #include #include -namespace xrpl::PeerFinder { +namespace xrpl::peer_finder { /** * Metadata for a Fixed slot. @@ -36,8 +36,8 @@ public: void failure(clock_type::time_point const& now) { - failures_ = std::min(failures_ + 1, Tuning::kConnectionBackoff.size() - 1); - when_ = now + std::chrono::minutes(Tuning::kConnectionBackoff[failures_]); + failures_ = std::min(failures_ + 1, tuning::kConnectionBackoff.size() - 1); + when_ = now + std::chrono::minutes(tuning::kConnectionBackoff[failures_]); } /** @@ -55,4 +55,4 @@ private: std::size_t failures_{0}; }; -} // namespace xrpl::PeerFinder +} // namespace xrpl::peer_finder diff --git a/include/xrpl/peerfinder/detail/Handouts.h b/include/xrpl/peerfinder/detail/Handouts.h index cb5fd7f850..c20d4b2139 100644 --- a/include/xrpl/peerfinder/detail/Handouts.h +++ b/include/xrpl/peerfinder/detail/Handouts.h @@ -12,7 +12,7 @@ #include #include -namespace xrpl::PeerFinder { +namespace xrpl::peer_finder { namespace detail { @@ -28,7 +28,7 @@ template std::size_t handoutOne(Target& t, HopContainer& h) { - XRPL_ASSERT(!t.full(), "xrpl::PeerFinder::detail::handoutOne : target is not full"); + XRPL_ASSERT(!t.full(), "xrpl::peer_finder::detail::handoutOne : target is not full"); for (auto it = h.begin(); it != h.end(); ++it) { auto const& e = *it; @@ -95,7 +95,7 @@ public: [[nodiscard]] bool full() const { - return list_.size() >= Tuning::kRedirectEndpointCount; + return list_.size() >= tuning::kRedirectEndpointCount; } [[nodiscard]] SlotImp::ptr const& @@ -124,7 +124,7 @@ private: template RedirectHandouts::RedirectHandouts(SlotImp::ptr slot) : slot_(std::move(slot)) { - list_.reserve(Tuning::kRedirectEndpointCount); + list_.reserve(tuning::kRedirectEndpointCount); } template @@ -138,7 +138,7 @@ RedirectHandouts::tryInsert(Endpoint const& ep) // addresses in a peer HTTP handshake instead of // the tmENDPOINTS message. // - if (ep.hops > Tuning::kMaxHops) + if (ep.hops > tuning::kMaxHops) return false; // Don't send them our address @@ -181,7 +181,7 @@ public: [[nodiscard]] bool full() const { - return list_.size() >= Tuning::kNumberOfEndpoints; + return list_.size() >= tuning::kNumberOfEndpoints; } void @@ -210,7 +210,7 @@ private: template SlotHandouts::SlotHandouts(SlotImp::ptr slot) : slot_(std::move(slot)) { - list_.reserve(Tuning::kNumberOfEndpoints); + list_.reserve(tuning::kNumberOfEndpoints); } template @@ -220,7 +220,7 @@ SlotHandouts::tryInsert(Endpoint const& ep) if (full()) return false; - if (ep.hops > Tuning::kMaxHops) + if (ep.hops > tuning::kMaxHops) return false; if (slot_->recent.filter(ep.address, ep.hops)) @@ -259,9 +259,9 @@ class ConnectHandouts public: // Keeps track of addresses we have made outgoing connections // to, for the purposes of not connecting to them too frequently. - using Squelches = beast::aged_set; + using Squelches = beast::aged_set; - using list_type = std::vector; + using list_type = std::vector; private: std::size_t needed_; @@ -274,7 +274,7 @@ public: template bool - tryInsert(beast::IP::Endpoint const& endpoint); + tryInsert(beast::ip::Endpoint const& endpoint); [[nodiscard]] bool empty() const @@ -316,13 +316,13 @@ ConnectHandouts::ConnectHandouts(std::size_t needed, Squelches& squelches) template bool -ConnectHandouts::tryInsert(beast::IP::Endpoint const& endpoint) +ConnectHandouts::tryInsert(beast::ip::Endpoint const& endpoint) { if (full()) return false; // Make sure the address isn't already in our list - if (std::ranges::any_of(list_, [&endpoint](beast::IP::Endpoint const& other) { + if (std::ranges::any_of(list_, [&endpoint](beast::ip::Endpoint const& other) { // Ignore port for security reasons return other.address() == endpoint.address(); })) @@ -341,4 +341,4 @@ ConnectHandouts::tryInsert(beast::IP::Endpoint const& endpoint) return true; } -} // namespace xrpl::PeerFinder +} // namespace xrpl::peer_finder diff --git a/include/xrpl/peerfinder/detail/Livecache.h b/include/xrpl/peerfinder/detail/Livecache.h index cac284d1cc..ec797065e5 100644 --- a/include/xrpl/peerfinder/detail/Livecache.h +++ b/include/xrpl/peerfinder/detail/Livecache.h @@ -29,7 +29,7 @@ #include #include -namespace xrpl::PeerFinder { +namespace xrpl::peer_finder { template class Livecache; @@ -188,10 +188,10 @@ class Livecache : protected detail::LivecacheBase { private: using cache_type = beast::aged_map< - beast::IP::Endpoint, + beast::ip::Endpoint, Element, std::chrono::steady_clock, - std::less, + std::less, Allocator>; beast::Journal journal_; @@ -220,8 +220,8 @@ public: // but not given out (since they would exceed maxHops). They // are used for automatic connection attempts. // - using Histogram = std::array; - using lists_type = std::array; + using Histogram = std::array; + using lists_type = std::array; template struct Transform @@ -400,7 +400,7 @@ Livecache::expire() { std::size_t n(0); typename cache_type::time_point const expired( - cache_.clock().now() - Tuning::kLiveCacheSecondsToLive); + cache_.clock().now() - tuning::kLiveCacheSecondsToLive); for (auto iter(cache_.chronological.begin()); iter != cache_.chronological.end() && iter.when() <= expired;) { @@ -427,8 +427,8 @@ Livecache::insert(Endpoint const& ep) // when redirecting. // XRPL_ASSERT( - ep.hops <= (Tuning::kMaxHops + 1), - "xrpl::PeerFinder::Livecache::insert : maximum input hops"); + ep.hops <= (tuning::kMaxHops + 1), + "xrpl::peer_finder::Livecache::insert : maximum input hops"); auto result = cache_.emplace(ep.address, ep); Element& e(result.first->second); if (result.second) @@ -468,7 +468,7 @@ void Livecache::onWrite(beast::PropertyStream::Map& map) { typename cache_type::time_point const expired( - cache_.clock().now() - Tuning::kLiveCacheSecondsToLive); + cache_.clock().now() - tuning::kLiveCacheSecondsToLive); map["size"] = size(); map["hist"] = hops.histogram(); beast::PropertyStream::Set set("entries", map); @@ -527,8 +527,8 @@ void Livecache::HopsT::insert(Element& e) { XRPL_ASSERT( - e.endpoint.hops <= Tuning::kMaxHops + 1, - "xrpl::PeerFinder::Livecache::HopsT::insert : maximum input hops"); + e.endpoint.hops <= tuning::kMaxHops + 1, + "xrpl::peer_finder::Livecache::HopsT::insert : maximum input hops"); // This has security implications without a shuffle lists_[e.endpoint.hops].push_front(e); ++hist_[e.endpoint.hops]; @@ -539,8 +539,8 @@ void Livecache::HopsT::reinsert(Element& e, std::uint32_t numHops) { XRPL_ASSERT( - numHops <= Tuning::kMaxHops + 1, - "xrpl::PeerFinder::Livecache::HopsT::reinsert : maximum hops input"); + numHops <= tuning::kMaxHops + 1, + "xrpl::peer_finder::Livecache::HopsT::reinsert : maximum hops input"); auto& list = lists_[e.endpoint.hops]; list.erase(list.iterator_to(e)); @@ -561,4 +561,4 @@ Livecache::HopsT::remove(Element& e) list.erase(list.iterator_to(e)); } -} // namespace xrpl::PeerFinder +} // namespace xrpl::peer_finder diff --git a/include/xrpl/peerfinder/detail/Logic.h b/include/xrpl/peerfinder/detail/Logic.h index c623263884..4821054280 100644 --- a/include/xrpl/peerfinder/detail/Logic.h +++ b/include/xrpl/peerfinder/detail/Logic.h @@ -43,7 +43,7 @@ #include #include -namespace xrpl::PeerFinder { +namespace xrpl::peer_finder { /** * The Logic for maintaining the list of Slot addresses. @@ -57,7 +57,7 @@ public: // Maps remote endpoints to slots. Since a slot has a // remote endpoint upon construction, this holds all counts_. // - using Slots = std::map>; + using Slots = std::map>; beast::Journal journal; clock_type& clock; @@ -81,7 +81,7 @@ private: Counts counts_; // A list of slots that should always be connected - std::map fixed_; + std::map fixed_; public: // Live livecache from mtENDPOINTS messages @@ -96,7 +96,7 @@ public: // The addresses (but not port) we are connected to. This includes // outgoing connection attempts. Note that this set can contain // duplicates (since the port is not set) - std::multiset connectedAddresses; + std::multiset connectedAddresses; // Set of public keys belonging to active peers std::set keys; @@ -170,13 +170,13 @@ public: } void - addFixedPeer(std::string_view name, beast::IP::Endpoint const& ep) + addFixedPeer(std::string_view name, beast::ip::Endpoint const& ep) { - addFixedPeer(name, std::vector{ep}); + addFixedPeer(name, std::vector{ep}); } void - addFixedPeer(std::string_view name, std::vector const& addresses) + addFixedPeer(std::string_view name, std::vector const& addresses) { std::scoped_lock const _(lock); @@ -213,8 +213,8 @@ public: // Called when the Checker completes a connectivity test void checkComplete( - beast::IP::Endpoint const& remoteAddress, - beast::IP::Endpoint const& checkedAddress, + beast::ip::Endpoint const& remoteAddress, + beast::ip::Endpoint const& checkedAddress, boost::system::error_code ec) { if (ec == boost::asio::error::operation_aborted) @@ -256,8 +256,8 @@ public: std::pair newInboundSlot( - beast::IP::Endpoint const& localEndpoint, - beast::IP::Endpoint const& remoteEndpoint) + beast::ip::Endpoint const& localEndpoint, + beast::ip::Endpoint const& remoteEndpoint) { JLOG(journal.debug()) << std::left << std::setw(18) << "Logic accept" << remoteEndpoint << " on local " << localEndpoint; @@ -293,7 +293,7 @@ public: // Remote address must not already exist XRPL_ASSERT( result.second, - "xrpl::PeerFinder::Logic::new_inbound_slot : remote endpoint " + "xrpl::peer_finder::Logic::new_inbound_slot : remote endpoint " "inserted"); // Add to the connected address list connectedAddresses.emplace(remoteEndpoint.address()); @@ -306,7 +306,7 @@ public: // Can't check for self-connect because we don't know the local endpoint std::pair - newOutboundSlot(beast::IP::Endpoint const& remoteEndpoint) + newOutboundSlot(beast::ip::Endpoint const& remoteEndpoint) { JLOG(journal.debug()) << std::left << std::setw(18) << "Logic connect " << remoteEndpoint; @@ -329,7 +329,7 @@ public: // Remote address must not already exist XRPL_ASSERT( result.second, - "xrpl::PeerFinder::Logic::new_outbound_slot : remote endpoint " + "xrpl::peer_finder::Logic::new_outbound_slot : remote endpoint " "inserted"); // Add to the connected address list @@ -342,7 +342,7 @@ public: } bool - onConnected(SlotImp::ptr const& slot, beast::IP::Endpoint const& localEndpoint) + onConnected(SlotImp::ptr const& slot, beast::ip::Endpoint const& localEndpoint) { beast::WrappedSink sink{journal.sink(), slot->prefix()}; beast::Journal const journal{sink}; @@ -354,7 +354,7 @@ public: // The object must exist in our table XRPL_ASSERT( slots.contains(slot->remoteEndpoint()), - "xrpl::PeerFinder::Logic::onConnected : valid slot input"); + "xrpl::peer_finder::Logic::onConnected : valid slot input"); // Assign the local endpoint now that it's known slot->localEndpoint(localEndpoint); @@ -365,7 +365,7 @@ public: { XRPL_ASSERT( iter->second->localEndpoint() == slot->remoteEndpoint(), - "xrpl::PeerFinder::Logic::onConnected : local and remote " + "xrpl::peer_finder::Logic::onConnected : local and remote " "endpoints do match"); JLOG(journal.warn()) << "Logic dropping as self connect"; return false; @@ -393,11 +393,11 @@ public: // The object must exist in our table XRPL_ASSERT( slots.contains(slot->remoteEndpoint()), - "xrpl::PeerFinder::Logic::activate : valid slot input"); + "xrpl::peer_finder::Logic::activate : valid slot input"); // Must be accepted or connected XRPL_ASSERT( slot->state() == Slot::State::Accept || slot->state() == Slot::State::Connected, - "xrpl::PeerFinder::Logic::activate : valid slot state"); + "xrpl::peer_finder::Logic::activate : valid slot state"); // Check for duplicate connection by key if (keys.contains(key)) @@ -425,7 +425,7 @@ public: { [[maybe_unused]] bool const inserted = keys.insert(key).second; // Public key must not already exist - XRPL_ASSERT(inserted, "xrpl::PeerFinder::Logic::activate : public key inserted"); + XRPL_ASSERT(inserted, "xrpl::peer_finder::Logic::activate : public key inserted"); } // Change state and update counts @@ -443,7 +443,7 @@ public: if (iter == fixed_.end()) { logicError( - "PeerFinder::Logic::activate(): remote_endpoint " + "peer_finder::Logic::activate(): remote_endpoint " "missing from fixed_"); } @@ -476,10 +476,10 @@ public: // VFALCO TODO This should add the returned addresses to the // squelch list in one go once the list is built, // rather than having each module add to the squelch list. - std::vector + std::vector autoconnect() { - std::vector none; + std::vector none; std::scoped_lock const _(lock); @@ -635,7 +635,7 @@ public: // either. ipv6 has a slightly more compact string // representation of 0, so use that for self entries. ep.address = - beast::IP::Endpoint(beast::IP::AddressV6()).atPort(config_.listeningPort); + beast::ip::Endpoint(beast::ip::AddressV6()).atPort(config_.listeningPort); for (auto& t : targets) t.insert(ep); } @@ -656,7 +656,7 @@ public: result.emplace_back(slot, list); } - whenBroadcast = now + Tuning::kSecondsPerMessage; + whenBroadcast = now + tuning::kSecondsPerMessage; } return result; @@ -675,7 +675,7 @@ public: entry.second->expire(); // Expire the recent attempts table - beast::expire(squelches, Tuning::kRecentAttemptDuration); + beast::expire(squelches, tuning::kRecentAttemptDuration); bootcache.periodicActivity(); } @@ -692,7 +692,7 @@ public: Endpoint& ep(*iter); // Enforce hop limit - if (ep.hops > Tuning::kMaxHops) + if (ep.hops > tuning::kMaxHops) { JLOG(journal.debug()) << std::left << std::setw(18) << "Endpoints drop " << ep.address << " for excess hops " << ep.hops; @@ -754,10 +754,10 @@ public: beast::Journal const journal{sink}; // If we're sent too many endpoints, sample them at random: - if (list.size() > Tuning::kNumberOfEndpointsMax) + if (list.size() > tuning::kNumberOfEndpointsMax) { std::shuffle(list.begin(), list.end(), defaultPrng()); - list.resize(Tuning::kNumberOfEndpointsMax); + list.resize(tuning::kNumberOfEndpointsMax); } JLOG(journal.trace()) << "Endpoints contained " << list.size() @@ -768,12 +768,12 @@ public: // The object must exist in our table XRPL_ASSERT( slots.contains(slot->remoteEndpoint()), - "xrpl::PeerFinder::Logic::onEndpoints : valid slot input"); + "xrpl::peer_finder::Logic::onEndpoints : valid slot input"); // Must be handshaked! XRPL_ASSERT( slot->state() == Slot::State::Active, - "xrpl::PeerFinder::Logic::onEndpoints : valid slot state"); + "xrpl::peer_finder::Logic::onEndpoints : valid slot state"); clock_type::time_point const now(clock.now()); @@ -785,7 +785,7 @@ public: for (auto const& ep : list) { - XRPL_ASSERT(ep.hops, "xrpl::PeerFinder::Logic::onEndpoints : nonzero hops"); + XRPL_ASSERT(ep.hops, "xrpl::peer_finder::Logic::onEndpoints : nonzero hops"); slot->recent.insert(ep.address, ep.hops); @@ -837,7 +837,7 @@ public: bootcache.insert(ep.address); } - slot->whenAcceptEndpoints = now + Tuning::kSecondsPerMessage; + slot->whenAcceptEndpoints = now + tuning::kSecondsPerMessage; } //-------------------------------------------------------------------------- @@ -851,7 +851,7 @@ public: if (iter == slots.end()) { logicError( - "PeerFinder::Logic::remove(): remote_endpoint " + "peer_finder::Logic::remove(): remote_endpoint " "missing from slots_"); } @@ -866,7 +866,7 @@ public: if (iter == keys.end()) { logicError( - "PeerFinder::Logic::remove(): public_key missing " + "peer_finder::Logic::remove(): public_key missing " "from keys_"); } @@ -879,7 +879,7 @@ public: if (iter == connectedAddresses.end()) { logicError( - "PeerFinder::Logic::remove(): remote_endpoint " + "peer_finder::Logic::remove(): remote_endpoint " "address missing from connectedAddresses_"); } @@ -907,7 +907,7 @@ public: if (iter == fixed_.end()) { logicError( - "PeerFinder::Logic::on_closed(): remote_endpoint " + "peer_finder::Logic::on_closed(): remote_endpoint " "missing from fixed_"); } @@ -943,7 +943,7 @@ public: // LCOV_EXCL_START default: UNREACHABLE( - "xrpl::PeerFinder::Logic::on_closed : invalid slot " + "xrpl::peer_finder::Logic::on_closed : invalid slot " "state"); break; // LCOV_EXCL_STOP @@ -968,17 +968,17 @@ public: // Returns `true` if the address matches a fixed slot address // Must have the lock held bool - fixed(beast::IP::Endpoint const& endpoint) const + fixed(beast::ip::Endpoint const& endpoint) const { return std::ranges::any_of( fixed_, [&endpoint](auto const& entry) { return entry.first == endpoint; }); } // Returns `true` if the address matches a fixed slot address - // Note that this does not use the port information in the IP::Endpoint + // Note that this does not use the port information in the ip::Endpoint // Must have the lock held bool - fixed(beast::IP::Address const& address) const + fixed(beast::ip::Address const& address) const { return std::ranges::any_of( fixed_, [&address](auto const& entry) { return entry.first.address() == address; }); @@ -1097,9 +1097,9 @@ public: // //-------------------------------------------------------------------------- - // Returns true if the IP::Endpoint contains no invalid data. + // Returns true if the ip::Endpoint contains no invalid data. bool - isValidAddress(beast::IP::Endpoint const& address) + isValidAddress(beast::ip::Endpoint const& address) { if (isUnspecified(address)) return false; @@ -1220,7 +1220,7 @@ Logic::onRedirects( { std::scoped_lock const _(lock); std::size_t n = 0; - for (; first != last && n < Tuning::kMaxRedirects; ++first, ++n) + for (; first != last && n < tuning::kMaxRedirects; ++first, ++n) bootcache.insert(beast::IPAddressConversion::fromAsio(*first)); if (n > 0) { @@ -1229,4 +1229,4 @@ Logic::onRedirects( } } -} // namespace xrpl::PeerFinder +} // namespace xrpl::peer_finder diff --git a/include/xrpl/peerfinder/detail/SlotImp.h b/include/xrpl/peerfinder/detail/SlotImp.h index 35c61b13cf..db86183f64 100644 --- a/include/xrpl/peerfinder/detail/SlotImp.h +++ b/include/xrpl/peerfinder/detail/SlotImp.h @@ -12,7 +12,7 @@ #include #include -namespace xrpl::PeerFinder { +namespace xrpl::peer_finder { class SlotImp : public Slot { @@ -21,13 +21,13 @@ public: // inbound SlotImp( - beast::IP::Endpoint const& localEndpoint, - beast::IP::Endpoint remoteEndpoint, + beast::ip::Endpoint const& localEndpoint, + beast::ip::Endpoint remoteEndpoint, bool fixed, clock_type& clock); // outbound - SlotImp(beast::IP::Endpoint remoteEndpoint, bool fixed, clock_type& clock); + SlotImp(beast::ip::Endpoint remoteEndpoint, bool fixed, clock_type& clock); bool inbound() const override @@ -53,13 +53,13 @@ public: return state_; } - beast::IP::Endpoint const& + beast::ip::Endpoint const& remoteEndpoint() const override { return remoteEndpoint_; } - std::optional const& + std::optional const& localEndpoint() const override { return localEndpoint_; @@ -93,13 +93,13 @@ public: } void - localEndpoint(beast::IP::Endpoint const& endpoint) + localEndpoint(beast::ip::Endpoint const& endpoint) { localEndpoint_ = endpoint; } void - remoteEndpoint(beast::IP::Endpoint const& endpoint) + remoteEndpoint(beast::ip::Endpoint const& endpoint) { remoteEndpoint_ = endpoint; } @@ -140,20 +140,20 @@ public: * sending a slot the same address too frequently. */ void - insert(beast::IP::Endpoint const& ep, std::uint32_t hops); + insert(beast::ip::Endpoint const& ep, std::uint32_t hops); /** * Returns `true` if we should not send endpoint to the slot. */ bool - filter(beast::IP::Endpoint const& ep, std::uint32_t hops); + filter(beast::ip::Endpoint const& ep, std::uint32_t hops); private: void expire(); friend class SlotImp; - beast::aged_unordered_map cache_; + beast::aged_unordered_map cache_; } recent; void @@ -167,8 +167,8 @@ private: bool const fixed_; bool reserved_; State state_; - beast::IP::Endpoint remoteEndpoint_; - std::optional localEndpoint_; + beast::ip::Endpoint remoteEndpoint_; + std::optional localEndpoint_; std::optional publicKey_; static std::int32_t constexpr kUnknownPort = -1; @@ -196,4 +196,4 @@ public: clock_type::time_point whenAcceptEndpoints; }; -} // namespace xrpl::PeerFinder +} // namespace xrpl::peer_finder diff --git a/include/xrpl/peerfinder/detail/Source.h b/include/xrpl/peerfinder/detail/Source.h index 5cdb535bdd..09aa4e216a 100644 --- a/include/xrpl/peerfinder/detail/Source.h +++ b/include/xrpl/peerfinder/detail/Source.h @@ -7,7 +7,7 @@ #include -namespace xrpl::PeerFinder { +namespace xrpl::peer_finder { /** * A static or dynamic source of peer addresses. @@ -46,4 +46,4 @@ public: fetch(Results& results, beast::Journal journal) = 0; }; -} // namespace xrpl::PeerFinder +} // namespace xrpl::peer_finder diff --git a/include/xrpl/peerfinder/detail/SourceStrings.h b/include/xrpl/peerfinder/detail/SourceStrings.h index 325a024764..e9783c775f 100644 --- a/include/xrpl/peerfinder/detail/SourceStrings.h +++ b/include/xrpl/peerfinder/detail/SourceStrings.h @@ -6,7 +6,7 @@ #include #include -namespace xrpl::PeerFinder { +namespace xrpl::peer_finder { /** * Provides addresses from a static set of strings. @@ -22,4 +22,4 @@ public: make(std::string const& name, Strings const& strings); }; -} // namespace xrpl::PeerFinder +} // namespace xrpl::peer_finder diff --git a/include/xrpl/peerfinder/detail/Store.h b/include/xrpl/peerfinder/detail/Store.h index 9393ef6c2b..1f9352c6ec 100644 --- a/include/xrpl/peerfinder/detail/Store.h +++ b/include/xrpl/peerfinder/detail/Store.h @@ -6,7 +6,7 @@ #include #include -namespace xrpl::PeerFinder { +namespace xrpl::peer_finder { /** * Abstract persistence for PeerFinder data. @@ -17,7 +17,7 @@ public: virtual ~Store() = default; // load the bootstrap cache - using load_callback = std::function; + using load_callback = std::function; virtual std::size_t load(load_callback const& cb) = 0; @@ -26,11 +26,11 @@ public: { explicit Entry() = default; - beast::IP::Endpoint endpoint; + beast::ip::Endpoint endpoint; int valence{}; }; virtual void save(std::vector const& v) = 0; }; -} // namespace xrpl::PeerFinder +} // namespace xrpl::peer_finder diff --git a/include/xrpl/peerfinder/detail/Tuning.h b/include/xrpl/peerfinder/detail/Tuning.h index ea4637dd9d..b4ccfae167 100644 --- a/include/xrpl/peerfinder/detail/Tuning.h +++ b/include/xrpl/peerfinder/detail/Tuning.h @@ -9,7 +9,7 @@ * Heuristically tuned constants. */ /** @{ */ -namespace xrpl::PeerFinder::Tuning { +namespace xrpl::peer_finder::tuning { //--------------------------------------------------------- // @@ -111,5 +111,5 @@ constexpr std::chrono::seconds kLiveCacheSecondsToLive(30); // Note that we ignore the port for purposes of comparison. constexpr std::chrono::seconds kRecentAttemptDuration(60); -} // namespace xrpl::PeerFinder::Tuning +} // namespace xrpl::peer_finder::tuning /** @} */ diff --git a/include/xrpl/peerfinder/make_Manager.h b/include/xrpl/peerfinder/make_Manager.h index 5da372e588..e514734d8b 100644 --- a/include/xrpl/peerfinder/make_Manager.h +++ b/include/xrpl/peerfinder/make_Manager.h @@ -10,7 +10,7 @@ #include -namespace xrpl::PeerFinder { +namespace xrpl::peer_finder { /** * @brief Create a new Manager. @@ -33,4 +33,4 @@ makeManager( Store& store, beast::insight::Collector::ptr const& collector); -} // namespace xrpl::PeerFinder +} // namespace xrpl::peer_finder diff --git a/include/xrpl/protocol/ApiVersion.h b/include/xrpl/protocol/ApiVersion.h index c3292e6074..b52e705b38 100644 --- a/include/xrpl/protocol/ApiVersion.h +++ b/include/xrpl/protocol/ApiVersion.h @@ -33,7 +33,7 @@ namespace xrpl { * Command line Requests use apiCommandLineVersion. */ -namespace RPC { +namespace rpc { template static constexpr std::integral_constant kApiVersion = {}; @@ -60,7 +60,7 @@ static_assert(kApiMaximumValidVersion >= kApiMaximumSupportedVersion); inline void setVersion(json::Value& parent, unsigned int apiVersion, bool betaEnabled) { - XRPL_ASSERT(apiVersion != kApiInvalidVersion, "xrpl::RPC::setVersion : input is valid"); + XRPL_ASSERT(apiVersion != kApiInvalidVersion, "xrpl::rpc::setVersion : input is valid"); auto& retObj = parent[jss::version] = json::ValueType::Object; @@ -99,12 +99,12 @@ setVersion(json::Value& parent, unsigned int apiVersion, bool betaEnabled) inline unsigned int getAPIVersionNumber(json::Value const& jv, bool betaEnabled) { - static json::Value const kMinVersion(RPC::kApiMinimumSupportedVersion); + static json::Value const kMinVersion(rpc::kApiMinimumSupportedVersion); json::Value const maxVersion( - betaEnabled ? RPC::kApiBetaVersion : RPC::kApiMaximumSupportedVersion); + betaEnabled ? rpc::kApiBetaVersion : rpc::kApiMaximumSupportedVersion); if (!jv.isObject() || !jv.isMember(jss::api_version)) - return RPC::kApiVersionIfUnspecified; + return rpc::kApiVersionIfUnspecified; try { @@ -113,33 +113,33 @@ getAPIVersionNumber(json::Value const& jv, bool betaEnabled) { case json::ValueType::Int: if (rawVersion.asInt() < 0) - return RPC::kApiInvalidVersion; + return rpc::kApiInvalidVersion; [[fallthrough]]; case json::ValueType::UInt: { auto const apiVersion = rawVersion.asUInt(); if (apiVersion < kMinVersion || apiVersion > maxVersion) - return RPC::kApiInvalidVersion; + return rpc::kApiInvalidVersion; return apiVersion; } default: - return RPC::kApiInvalidVersion; + return rpc::kApiInvalidVersion; } } catch (...) { - return RPC::kApiInvalidVersion; + return rpc::kApiInvalidVersion; } } -} // namespace RPC +} // namespace rpc template void forApiVersions(Fn const& fn, Args&&... args) requires // (MaxVer >= MinVer) && // - (MinVer >= RPC::kApiMinimumSupportedVersion) && // - (RPC::kApiMaximumValidVersion >= MaxVer) && requires { + (MinVer >= rpc::kApiMinimumSupportedVersion) && // + (rpc::kApiMaximumValidVersion >= MaxVer) && requires { fn(std::integral_constant{}, std::forward(args)...); fn(std::integral_constant{}, std::forward(args)...); } @@ -158,11 +158,11 @@ template void forAllApiVersions(Fn const& fn, Args&&... args) requires requires { - forApiVersions( + forApiVersions( fn, std::forward(args)...); } { - forApiVersions( + forApiVersions( fn, std::forward(args)...); } diff --git a/include/xrpl/protocol/BuildInfo.h b/include/xrpl/protocol/BuildInfo.h index 18ba20f23c..c3f90d8f9f 100644 --- a/include/xrpl/protocol/BuildInfo.h +++ b/include/xrpl/protocol/BuildInfo.h @@ -8,7 +8,7 @@ * Versioning information for this build. */ // VFALCO The namespace is deprecated -namespace xrpl::BuildInfo { +namespace xrpl::build_info { /** * Server version. @@ -84,4 +84,4 @@ isXrpldVersion(std::uint64_t version); bool isNewerVersion(std::uint64_t version); -} // namespace xrpl::BuildInfo +} // namespace xrpl::build_info diff --git a/include/xrpl/protocol/ErrorCodes.h b/include/xrpl/protocol/ErrorCodes.h index 8ac7c8c58f..465b6d711f 100644 --- a/include/xrpl/protocol/ErrorCodes.h +++ b/include/xrpl/protocol/ErrorCodes.h @@ -167,7 +167,7 @@ enum WarningCodeI { // VFALCO NOTE these should probably not be in the RPC namespace. -namespace RPC { +namespace rpc { /** * Maps an rpc error code to its token, default message, and HTTP status. @@ -337,7 +337,7 @@ containsError(json::Value const& json); int errorCodeHttpStatus(ErrorCodeI code); -} // namespace RPC +} // namespace rpc /** * Returns a single string with the contents of an RPC error. diff --git a/include/xrpl/protocol/MultiApiJson.h b/include/xrpl/protocol/MultiApiJson.h index 9a4882ec55..a0029fa491 100644 --- a/include/xrpl/protocol/MultiApiJson.h +++ b/include/xrpl/protocol/MultiApiJson.h @@ -188,6 +188,6 @@ struct MultiApiJson // Wrapper for Json for all supported API versions. using MultiApiJson = - detail::MultiApiJson; + detail::MultiApiJson; } // namespace xrpl diff --git a/include/xrpl/protocol/NFTSyntheticSerializer.h b/include/xrpl/protocol/NFTSyntheticSerializer.h index bef05b9a8f..df4fedb707 100644 --- a/include/xrpl/protocol/NFTSyntheticSerializer.h +++ b/include/xrpl/protocol/NFTSyntheticSerializer.h @@ -6,7 +6,7 @@ #include -namespace xrpl::RPC { +namespace xrpl::rpc { /** * Adds common synthetic fields to transaction-related JSON responses @@ -16,4 +16,4 @@ void insertNFTSyntheticInJson(json::Value&, std::shared_ptr const&, TxMeta const&); /** @} */ -} // namespace xrpl::RPC +} // namespace xrpl::rpc diff --git a/include/xrpl/protocol/Protocol.h b/include/xrpl/protocol/Protocol.h index 9938a9b768..567f66d339 100644 --- a/include/xrpl/protocol/Protocol.h +++ b/include/xrpl/protocol/Protocol.h @@ -139,7 +139,7 @@ tenthBipsOfValue(T value, TenthBips bips) return value * bips.value() / kTenthBipsPerUnity.value(); } -namespace Lending { +namespace lending { /** * The maximum management fee rate allowed by a loan broker in 1/10 bips. * @@ -236,7 +236,7 @@ static constexpr int kLoanPaymentsPerFeeIncrement = 5; * without an amendment */ static constexpr int kLoanMaximumPaymentsPerTransaction = 100; -} // namespace Lending +} // namespace lending /** * The maximum length of a URI inside an NFT diff --git a/include/xrpl/protocol/PublicKey.h b/include/xrpl/protocol/PublicKey.h index 98301af487..833078d741 100644 --- a/include/xrpl/protocol/PublicKey.h +++ b/include/xrpl/protocol/PublicKey.h @@ -260,7 +260,7 @@ calcAccountID(PublicKey const& pk); inline std::string getFingerprint( - beast::IP::Endpoint const& address, + beast::ip::Endpoint const& address, std::optional const& publicKey = std::nullopt, std::optional const& id = std::nullopt) { diff --git a/include/xrpl/protocol/XChainAttestations.h b/include/xrpl/protocol/XChainAttestations.h index 8f1c7a4ce3..ed8ffeb88e 100644 --- a/include/xrpl/protocol/XChainAttestations.h +++ b/include/xrpl/protocol/XChainAttestations.h @@ -20,7 +20,7 @@ namespace xrpl { -namespace Attestations { +namespace attestations { struct AttestationBase { @@ -227,7 +227,7 @@ struct CmpByCreateCount } }; -}; // namespace Attestations +}; // namespace attestations // Result when checking when two attestation match. enum class AttestationMatch { @@ -241,7 +241,7 @@ enum class AttestationMatch { struct XChainClaimAttestation { - using TSignedAttestation = Attestations::AttestationClaim; + using TSignedAttestation = attestations::AttestationClaim; static SField const& arrayFieldName; AccountID keyAccount; @@ -297,7 +297,7 @@ struct XChainClaimAttestation struct XChainCreateAccountAttestation { - using TSignedAttestation = Attestations::AttestationCreateAccount; + using TSignedAttestation = attestations::AttestationCreateAccount; static SField const& arrayFieldName; AccountID keyAccount; diff --git a/include/xrpl/resource/Charge.h b/include/xrpl/resource/Charge.h index 12ea548fd2..b5bb8dd52e 100644 --- a/include/xrpl/resource/Charge.h +++ b/include/xrpl/resource/Charge.h @@ -4,7 +4,7 @@ #include #include -namespace xrpl::Resource { +namespace xrpl::resource { /** * A consumption charge. @@ -32,7 +32,7 @@ public: label() const; /** - * Return the cost of the charge in Resource::Manager units. + * Return the cost of the charge in resource::Manager units. */ [[nodiscard]] value_type cost() const; @@ -60,4 +60,4 @@ private: std::ostream& operator<<(std::ostream& os, Charge const& v); -} // namespace xrpl::Resource +} // namespace xrpl::resource diff --git a/include/xrpl/resource/Consumer.h b/include/xrpl/resource/Consumer.h index 9abcbffc82..01539e3a39 100644 --- a/include/xrpl/resource/Consumer.h +++ b/include/xrpl/resource/Consumer.h @@ -8,7 +8,7 @@ #include #include -namespace xrpl::Resource { +namespace xrpl::resource { struct Entry; class Logic; @@ -96,4 +96,4 @@ private: std::ostream& operator<<(std::ostream& os, Consumer const& v); -} // namespace xrpl::Resource +} // namespace xrpl::resource diff --git a/include/xrpl/resource/Disposition.h b/include/xrpl/resource/Disposition.h index cd5bceafa5..28dd4edf62 100644 --- a/include/xrpl/resource/Disposition.h +++ b/include/xrpl/resource/Disposition.h @@ -1,6 +1,6 @@ #pragma once -namespace xrpl::Resource { +namespace xrpl::resource { /** * The disposition of a consumer after applying a load charge. @@ -24,4 +24,4 @@ enum class Disposition { Drop }; -} // namespace xrpl::Resource +} // namespace xrpl::resource diff --git a/include/xrpl/resource/Fees.h b/include/xrpl/resource/Fees.h index 5001b504d6..411169253d 100644 --- a/include/xrpl/resource/Fees.h +++ b/include/xrpl/resource/Fees.h @@ -2,7 +2,7 @@ #include -namespace xrpl::Resource { +namespace xrpl::resource { /** * Schedule of fees charged for imposing load on the server. @@ -31,4 +31,4 @@ extern Charge const kFeeWarning; // The cost of receiving a warning. extern Charge const kFeeDrop; // The cost of being dropped for excess load. /** @} */ -} // namespace xrpl::Resource +} // namespace xrpl::resource diff --git a/include/xrpl/resource/Gossip.h b/include/xrpl/resource/Gossip.h index 4ad5852de0..0d8ccb100c 100644 --- a/include/xrpl/resource/Gossip.h +++ b/include/xrpl/resource/Gossip.h @@ -4,7 +4,7 @@ #include -namespace xrpl::Resource { +namespace xrpl::resource { /** * Data format for exchanging consumption information across peers. @@ -21,10 +21,10 @@ struct Gossip explicit Item() = default; int balance{}; - beast::IP::Endpoint address; + beast::ip::Endpoint address; }; std::vector items; }; -} // namespace xrpl::Resource +} // namespace xrpl::resource diff --git a/include/xrpl/resource/README.md b/include/xrpl/resource/README.md index 545d4e9ca0..96b6c3d603 100644 --- a/include/xrpl/resource/README.md +++ b/include/xrpl/resource/README.md @@ -1,4 +1,4 @@ -# Resource::Manager +# resource::Manager The ResourceManager module has these responsibilities: @@ -36,7 +36,7 @@ to the general public. ## Consumer Types Consumers are placed into three classifications (as identified by the -Resource::Kind enumeration): +resource::Kind enumeration): - InBound, - OutBound, and @@ -72,6 +72,6 @@ drop connections to those IP addresses that occur commonly in the gossip. ## Access -In xrpld, the Application holds a unique instance of Resource::Manager, +In xrpld, the Application holds a unique instance of resource::Manager, which may be retrieved by calling the method `Application::getResourceManager()`. diff --git a/include/xrpl/resource/ResourceManager.h b/include/xrpl/resource/ResourceManager.h index 03aab60c75..267cfb16e3 100644 --- a/include/xrpl/resource/ResourceManager.h +++ b/include/xrpl/resource/ResourceManager.h @@ -14,7 +14,7 @@ #include #include -namespace xrpl::Resource { +namespace xrpl::resource { /** * Tracks load and resource consumption. @@ -32,10 +32,10 @@ public: * IP if proxied. */ virtual Consumer - newInboundEndpoint(beast::IP::Endpoint const& address) = 0; + newInboundEndpoint(beast::ip::Endpoint const& address) = 0; virtual Consumer newInboundEndpoint( - beast::IP::Endpoint const& address, + beast::ip::Endpoint const& address, bool const proxy, std::string_view forwardedFor) = 0; @@ -43,13 +43,13 @@ public: * Create a new endpoint keyed by outbound IP address and port. */ virtual Consumer - newOutboundEndpoint(beast::IP::Endpoint const& address) = 0; + newOutboundEndpoint(beast::ip::Endpoint const& address) = 0; /** * Create a new unlimited endpoint keyed by forwarded IP. */ virtual Consumer - newUnlimitedEndpoint(beast::IP::Endpoint const& address) = 0; + newUnlimitedEndpoint(beast::ip::Endpoint const& address) = 0; /** * Extract packaged consumer information for export. @@ -78,4 +78,4 @@ public: std::unique_ptr makeManager(beast::insight::Collector::ptr const& collector, beast::Journal journal); -} // namespace xrpl::Resource +} // namespace xrpl::resource diff --git a/include/xrpl/resource/detail/Entry.h b/include/xrpl/resource/detail/Entry.h index 1336bda6ab..ec5a328b8b 100644 --- a/include/xrpl/resource/detail/Entry.h +++ b/include/xrpl/resource/detail/Entry.h @@ -12,7 +12,7 @@ #include #include -namespace xrpl::Resource { +namespace xrpl::resource { using clock_type = beast::AbstractClock; @@ -91,4 +91,4 @@ operator<<(std::ostream& os, Entry const& v) return os; } -} // namespace xrpl::Resource +} // namespace xrpl::resource diff --git a/include/xrpl/resource/detail/Import.h b/include/xrpl/resource/detail/Import.h index b19dbc4d1a..c5366146c1 100644 --- a/include/xrpl/resource/detail/Import.h +++ b/include/xrpl/resource/detail/Import.h @@ -5,7 +5,7 @@ #include -namespace xrpl::Resource { +namespace xrpl::resource { /** * A set of imported consumer data from a gossip origin. @@ -32,4 +32,4 @@ struct Import std::vector items; }; -} // namespace xrpl::Resource +} // namespace xrpl::resource diff --git a/include/xrpl/resource/detail/Key.h b/include/xrpl/resource/detail/Key.h index a0f11422a7..180e868319 100644 --- a/include/xrpl/resource/detail/Key.h +++ b/include/xrpl/resource/detail/Key.h @@ -7,17 +7,17 @@ #include #include -namespace xrpl::Resource { +namespace xrpl::resource { // The consumer key struct Key { Kind kind; - beast::IP::Endpoint address; + beast::ip::Endpoint address; Key() = delete; - Key(Kind k, beast::IP::Endpoint addr) : kind(k), address(std::move(addr)) + Key(Kind k, beast::ip::Endpoint addr) : kind(k), address(std::move(addr)) { } @@ -47,4 +47,4 @@ struct Key }; }; -} // namespace xrpl::Resource +} // namespace xrpl::resource diff --git a/include/xrpl/resource/detail/Kind.h b/include/xrpl/resource/detail/Kind.h index ce2e0773cf..9af760d252 100644 --- a/include/xrpl/resource/detail/Kind.h +++ b/include/xrpl/resource/detail/Kind.h @@ -1,6 +1,6 @@ #pragma once -namespace xrpl::Resource { +namespace xrpl::resource { /** * Kind of consumer. @@ -12,4 +12,4 @@ namespace xrpl::Resource { */ enum class Kind { Inbound, Outbound, Unlimited }; -} // namespace xrpl::Resource +} // namespace xrpl::resource diff --git a/include/xrpl/resource/detail/Logic.h b/include/xrpl/resource/detail/Logic.h index 3f36ad84a3..aaaeb4fdd1 100644 --- a/include/xrpl/resource/detail/Logic.h +++ b/include/xrpl/resource/detail/Logic.h @@ -24,7 +24,7 @@ #include #include -namespace xrpl::Resource { +namespace xrpl::resource { class Logic { @@ -96,7 +96,7 @@ public: } Consumer - newInboundEndpoint(beast::IP::Endpoint const& address) + newInboundEndpoint(beast::ip::Endpoint const& address) { Entry* entry(nullptr); @@ -126,7 +126,7 @@ public: } Consumer - newOutboundEndpoint(beast::IP::Endpoint const& address) + newOutboundEndpoint(beast::ip::Endpoint const& address) { Entry* entry(nullptr); @@ -159,7 +159,7 @@ public: * enabled. */ Consumer - newUnlimitedEndpoint(beast::IP::Endpoint const& address) + newUnlimitedEndpoint(beast::ip::Endpoint const& address) { Entry* entry(nullptr); @@ -387,7 +387,7 @@ public: { std::scoped_lock const _(lock_); Entry& entry(iter->second); - XRPL_ASSERT(entry.refcount == 0, "xrpl::Resource::Logic::erase : entry not used"); + XRPL_ASSERT(entry.refcount == 0, "xrpl::resource::Logic::erase : entry not used"); inactive_.erase(inactive_.iteratorTo(entry)); table_.erase(iter); } @@ -421,7 +421,7 @@ public: default: // LCOV_EXCL_START UNREACHABLE( - "xrpl::Resource::Logic::release : invalid entry " + "xrpl::resource::Logic::release : invalid entry " "kind"); break; // LCOV_EXCL_STOP @@ -440,7 +440,7 @@ public: static_assert( kFeeLogAsWarn > kFeeLogAsInfo && kFeeLogAsInfo > kFeeLogAsDebug && kFeeLogAsDebug > 10); - static auto kGetStream = [](Resource::Charge::value_type cost, beast::Journal& journal) { + static auto kGetStream = [](resource::Charge::value_type cost, beast::Journal& journal) { if (cost >= kFeeLogAsWarn) return journal.warn(); if (cost >= kFeeLogAsInfo) @@ -564,4 +564,4 @@ public: } }; -} // namespace xrpl::Resource +} // namespace xrpl::resource diff --git a/include/xrpl/resource/detail/Tuning.h b/include/xrpl/resource/detail/Tuning.h index 62f7fa3f9d..d631aaddba 100644 --- a/include/xrpl/resource/detail/Tuning.h +++ b/include/xrpl/resource/detail/Tuning.h @@ -2,7 +2,7 @@ #include -namespace xrpl::Resource { +namespace xrpl::resource { /** * Tunable constants. @@ -26,4 +26,4 @@ static constexpr std::chrono::seconds kSecondsUntilExpiration{300}; // Number of seconds until imported gossip expires static constexpr std::chrono::seconds kGossipExpirationSeconds{30}; -} // namespace xrpl::Resource +} // namespace xrpl::resource diff --git a/include/xrpl/server/InfoSub.h b/include/xrpl/server/InfoSub.h index 2e9bd857c7..4bf88cd53b 100644 --- a/include/xrpl/server/InfoSub.h +++ b/include/xrpl/server/InfoSub.h @@ -62,7 +62,7 @@ public: using ref = std::shared_ptr const&; - using Consumer = Resource::Consumer; + using Consumer = resource::Consumer; public: /** diff --git a/include/xrpl/server/Session.h b/include/xrpl/server/Session.h index be8d9a497c..03ac767c25 100644 --- a/include/xrpl/server/Session.h +++ b/include/xrpl/server/Session.h @@ -52,7 +52,7 @@ public: /** * Returns the remote address of the connection. */ - virtual beast::IP::Endpoint + virtual beast::ip::Endpoint remoteAddress() = 0; /** diff --git a/include/xrpl/server/detail/BaseHTTPPeer.h b/include/xrpl/server/detail/BaseHTTPPeer.h index c7553c1da3..6020d3cc65 100644 --- a/include/xrpl/server/detail/BaseHTTPPeer.h +++ b/include/xrpl/server/detail/BaseHTTPPeer.h @@ -157,7 +157,7 @@ protected: return port_; } - beast::IP::Endpoint + beast::ip::Endpoint remoteAddress() override { return beast::IPAddressConversion::fromAsio(remoteAddress_); diff --git a/include/xrpl/server/detail/BaseWSPeer.h b/include/xrpl/server/detail/BaseWSPeer.h index 59a866ab8c..b1670865bd 100644 --- a/include/xrpl/server/detail/BaseWSPeer.h +++ b/include/xrpl/server/detail/BaseWSPeer.h @@ -196,7 +196,7 @@ BaseWSPeer::run() startTimer(); closeOnTimer_ = true; impl().ws_.set_option(boost::beast::websocket::stream_base::decorator([](auto& res) { - res.set(boost::beast::http::field::server, BuildInfo::getFullVersionString()); + res.set(boost::beast::http::field::server, build_info::getFullVersionString()); })); impl().ws_.async_accept( request_, bind_executor(strand_, [self = impl().shared_from_this()](error_code const& ec) { diff --git a/src/libxrpl/basics/ResolverAsio.cpp b/src/libxrpl/basics/ResolverAsio.cpp index 25e95b7fc5..53739fed8a 100644 --- a/src/libxrpl/basics/ResolverAsio.cpp +++ b/src/libxrpl/basics/ResolverAsio.cpp @@ -255,7 +255,7 @@ public: if (ec == boost::asio::error::operation_aborted) return; - std::vector addresses; + std::vector addresses; auto iter = results.begin(); // If we get an error message back, we don't return any @@ -283,7 +283,7 @@ public: // first attempt to parse as an endpoint (IP addr + port). // If that doesn't succeed, fall back to generic name + port parsing - if (auto const result = beast::IP::Endpoint::fromStringChecked(str)) + if (auto const result = beast::ip::Endpoint::fromStringChecked(str)) { return make_pair(result->address().to_string(), std::to_string(result->port())); } diff --git a/src/libxrpl/basics/StringUtilities.cpp b/src/libxrpl/basics/StringUtilities.cpp index f4edaf5aca..2b7deecb8e 100644 --- a/src/libxrpl/basics/StringUtilities.cpp +++ b/src/libxrpl/basics/StringUtilities.cpp @@ -74,7 +74,7 @@ parseUrl(ParsedUrl& pUrl, std::string const& strUrl) // We need to use Endpoint to parse the domain to // strip surrounding brackets from IPv6 addresses, // e.g. [::1] => ::1. - auto const result = beast::IP::Endpoint::fromStringChecked(domain); + auto const result = beast::ip::Endpoint::fromStringChecked(domain); pUrl.domain = result ? result->address().to_string() : domain; std::string const port = smMatch[5]; if (!port.empty()) diff --git a/src/libxrpl/beast/insight/StatsDCollector.cpp b/src/libxrpl/beast/insight/StatsDCollector.cpp index 3cff5d93b5..72fe6189a5 100644 --- a/src/libxrpl/beast/insight/StatsDCollector.cpp +++ b/src/libxrpl/beast/insight/StatsDCollector.cpp @@ -207,7 +207,7 @@ private: static constexpr auto kMaxPacketSize = 1472; Journal journal_; - IP::Endpoint address_; + ip::Endpoint address_; std::string prefix_; boost::asio::io_context ioContext_; std::optional> work_; @@ -222,13 +222,13 @@ private: std::thread thread_; static boost::asio::ip::udp::endpoint - toEndpoint(IP::Endpoint const& ep) + toEndpoint(ip::Endpoint const& ep) { return boost::asio::ip::udp::endpoint(ep.address(), ep.port()); } public: - StatsDCollectorImp(IP::Endpoint address, std::string prefix, Journal journal) + StatsDCollectorImp(ip::Endpoint address, std::string prefix, Journal journal) : journal_(journal) , address_(std::move(address)) , prefix_(std::move(prefix)) @@ -707,7 +707,7 @@ StatsDMeterImpl::doProcess() //------------------------------------------------------------------------------ std::shared_ptr -StatsDCollector::make(IP::Endpoint const& address, std::string const& prefix, Journal journal) +StatsDCollector::make(ip::Endpoint const& address, std::string const& prefix, Journal journal) { return std::make_shared(address, prefix, journal); } diff --git a/src/libxrpl/beast/net/IPAddressConversion.cpp b/src/libxrpl/beast/net/IPAddressConversion.cpp index c0a37d234e..bf24ef75c1 100644 --- a/src/libxrpl/beast/net/IPAddressConversion.cpp +++ b/src/libxrpl/beast/net/IPAddressConversion.cpp @@ -5,7 +5,7 @@ #include #include -namespace beast::IP { +namespace beast::ip { Endpoint fromAsio(boost::asio::ip::address const& address) @@ -31,4 +31,4 @@ toAsioEndpoint(Endpoint const& endpoint) return boost::asio::ip::tcp::endpoint{endpoint.address(), endpoint.port()}; } -} // namespace beast::IP +} // namespace beast::ip diff --git a/src/libxrpl/beast/net/IPAddressV4.cpp b/src/libxrpl/beast/net/IPAddressV4.cpp index f9b0c96022..2a59fe1cc4 100644 --- a/src/libxrpl/beast/net/IPAddressV4.cpp +++ b/src/libxrpl/beast/net/IPAddressV4.cpp @@ -1,6 +1,6 @@ #include -namespace beast::IP { +namespace beast::ip { bool isPrivate(AddressV4 const& addr) @@ -62,4 +62,4 @@ getClass(AddressV4 const& addr) return kTable[(addr.to_uint() & 0xE0000000) >> 29]; } -} // namespace beast::IP +} // namespace beast::ip diff --git a/src/libxrpl/beast/net/IPAddressV6.cpp b/src/libxrpl/beast/net/IPAddressV6.cpp index c75ccaf1cc..e5ef55065f 100644 --- a/src/libxrpl/beast/net/IPAddressV6.cpp +++ b/src/libxrpl/beast/net/IPAddressV6.cpp @@ -4,7 +4,7 @@ #include -namespace beast::IP { +namespace beast::ip { bool isPrivate(AddressV6 const& addr) @@ -58,4 +58,4 @@ isPublic(AddressV6 const& addr) return true; } -} // namespace beast::IP +} // namespace beast::ip diff --git a/src/libxrpl/beast/net/IPEndpoint.cpp b/src/libxrpl/beast/net/IPEndpoint.cpp index 5877151187..02ed5e37c5 100644 --- a/src/libxrpl/beast/net/IPEndpoint.cpp +++ b/src/libxrpl/beast/net/IPEndpoint.cpp @@ -14,7 +14,7 @@ #include #include -namespace beast::IP { +namespace beast::ip { Endpoint::Endpoint() : port_(0) { @@ -176,4 +176,4 @@ operator>>(std::istream& is, Endpoint& endpoint) return is; } -} // namespace beast::IP +} // namespace beast::ip diff --git a/src/libxrpl/ledger/helpers/LendingHelpers.cpp b/src/libxrpl/ledger/helpers/LendingHelpers.cpp index dac2c67181..89b03a03a7 100644 --- a/src/libxrpl/ledger/helpers/LendingHelpers.cpp +++ b/src/libxrpl/ledger/helpers/LendingHelpers.cpp @@ -131,7 +131,7 @@ isRounded(Asset const& asset, Number const& value, std::int32_t scale) roundToAsset(asset, value, scale, Number::RoundingMode::Upward); } -namespace Accrual { +namespace accrual { AccountingDeltas loanOriginationDeltas(Number const& principalRequested, Number const& interestDue) @@ -169,9 +169,9 @@ loanPaymentDeltas(LoanPaymentParts const& parts) .debtTotalDelta = (parts.principalPaid + parts.interestPaid) - parts.valueChange}; } -} // namespace Accrual +} // namespace accrual -namespace CashBasis { +namespace cash_basis { AccountingDeltas loanOriginationDeltas(Number const& principalRequested) @@ -196,7 +196,7 @@ loanPaymentDeltas(LoanPaymentParts const& parts) return {.assetsTotalDelta = parts.interestPaid, .debtTotalDelta = parts.principalPaid}; } -} // namespace CashBasis +} // namespace cash_basis namespace { @@ -219,8 +219,8 @@ loanOriginationDeltas( Number const& interestDue) { return cashBasisEnabled(vaultSle) - ? CashBasis::loanOriginationDeltas(principalRequested) - : Accrual::loanOriginationDeltas(principalRequested, interestDue); + ? cash_basis::loanOriginationDeltas(principalRequested) + : accrual::loanOriginationDeltas(principalRequested, interestDue); } bool @@ -235,21 +235,21 @@ loanOriginationExceedsVaultMaximum( return false; auto const vaultMaximum = vaultSle->at(sfAssetsMaximum); - return Accrual::loanOriginationExceedsVaultMaximum(vaultMaximum, vaultTotal, interestDue); + return accrual::loanOriginationExceedsVaultMaximum(vaultMaximum, vaultTotal, interestDue); } Number loanVaultExposure(SLE::const_ref vaultSle, SLE::const_ref loanSle) { - return cashBasisEnabled(vaultSle) ? CashBasis::loanVaultExposure(loanSle) - : Accrual::loanVaultExposure(loanSle); + return cashBasisEnabled(vaultSle) ? cash_basis::loanVaultExposure(loanSle) + : accrual::loanVaultExposure(loanSle); } AccountingDeltas loanPaymentDeltas(SLE::const_ref vaultSle, LoanPaymentParts const& parts) { - return cashBasisEnabled(vaultSle) ? CashBasis::loanPaymentDeltas(parts) - : Accrual::loanPaymentDeltas(parts); + return cashBasisEnabled(vaultSle) ? cash_basis::loanPaymentDeltas(parts) + : accrual::loanPaymentDeltas(parts); } namespace detail { @@ -1617,7 +1617,7 @@ makeRegularPayment( LoanPaymentType const paymentType, beast::Journal j) { - using namespace Lending; + using namespace lending; XRPL_ASSERT_PARTS( paymentType == LoanPaymentType::Regular || paymentType == LoanPaymentType::Overpayment, diff --git a/src/libxrpl/peerfinder/Bootcache.cpp b/src/libxrpl/peerfinder/Bootcache.cpp index a2a56b4d01..4f9b5fc816 100644 --- a/src/libxrpl/peerfinder/Bootcache.cpp +++ b/src/libxrpl/peerfinder/Bootcache.cpp @@ -16,7 +16,7 @@ #include #include -namespace xrpl::PeerFinder { +namespace xrpl::peer_finder { Bootcache::Bootcache(Store& store, clock_type& clock, beast::Journal journal) : store_(store), clock_(clock), journal_(journal), whenUpdate_(clock_.now()) @@ -78,7 +78,7 @@ void Bootcache::load() { clear(); - auto const n(store_.load([this](beast::IP::Endpoint const& endpoint, int valence) { + auto const n(store_.load([this](beast::ip::Endpoint const& endpoint, int valence) { auto const result(this->map_.insert(value_type(endpoint, valence))); if (!result.second) { @@ -96,7 +96,7 @@ Bootcache::load() } bool -Bootcache::insert(beast::IP::Endpoint const& endpoint) +Bootcache::insert(beast::ip::Endpoint const& endpoint) { auto const result(map_.insert(value_type(endpoint, 0))); if (result.second) @@ -109,7 +109,7 @@ Bootcache::insert(beast::IP::Endpoint const& endpoint) } bool -Bootcache::insertStatic(beast::IP::Endpoint const& endpoint) +Bootcache::insertStatic(beast::ip::Endpoint const& endpoint) { auto result(map_.insert(value_type(endpoint, kStaticValence))); @@ -130,7 +130,7 @@ Bootcache::insertStatic(beast::IP::Endpoint const& endpoint) } void -Bootcache::onSuccess(beast::IP::Endpoint const& endpoint) +Bootcache::onSuccess(beast::ip::Endpoint const& endpoint) { auto result(map_.insert(value_type(endpoint, 1))); if (result.second) @@ -144,7 +144,7 @@ Bootcache::onSuccess(beast::IP::Endpoint const& endpoint) ++entry.valence(); map_.erase(result.first); result = map_.insert(value_type(endpoint, entry)); - XRPL_ASSERT(result.second, "xrpl::PeerFinder::Bootcache::onSuccess : endpoint inserted"); + XRPL_ASSERT(result.second, "xrpl::peer_finder::Bootcache::onSuccess : endpoint inserted"); } Entry const& entry(result.first->right); JLOG(journal_.info()) << std::left << std::setw(18) << "Bootcache connect " << endpoint @@ -154,7 +154,7 @@ Bootcache::onSuccess(beast::IP::Endpoint const& endpoint) } void -Bootcache::onFailure(beast::IP::Endpoint const& endpoint) +Bootcache::onFailure(beast::ip::Endpoint const& endpoint) { auto result(map_.insert(value_type(endpoint, -1))); if (result.second) @@ -168,7 +168,7 @@ Bootcache::onFailure(beast::IP::Endpoint const& endpoint) --entry.valence(); map_.erase(result.first); result = map_.insert(value_type(endpoint, entry)); - XRPL_ASSERT(result.second, "xrpl::PeerFinder::Bootcache::onFailure : endpoint inserted"); + XRPL_ASSERT(result.second, "xrpl::peer_finder::Bootcache::onFailure : endpoint inserted"); } Entry const& entry(result.first->right); auto const n(std::abs(entry.valence())); @@ -201,11 +201,11 @@ Bootcache::onWrite(beast::PropertyStream::Map& map) void Bootcache::prune() { - if (size() <= Tuning::kBootcacheSize) + if (size() <= tuning::kBootcacheSize) return; // Calculate the amount to remove - auto count((size() * Tuning::kBootcachePrunePercent) / 100); + auto count((size() * tuning::kBootcachePrunePercent) / 100); decltype(count) pruned(0); // Work backwards because bimap doesn't handle @@ -215,7 +215,7 @@ Bootcache::prune() { --count; --iter; - beast::IP::Endpoint const& endpoint(iter->get_left()); + beast::ip::Endpoint const& endpoint(iter->get_left()); Entry const& entry(iter->get_right()); JLOG(journal_.trace()) << std::left << std::setw(18) << "Bootcache pruned" << endpoint << " at valence " << entry.valence(); @@ -244,7 +244,7 @@ Bootcache::update() store_.save(list); // Reset the flag and cooldown timer needsUpdate_ = false; - whenUpdate_ = clock_.now() + Tuning::kBootcacheCooldownTime; + whenUpdate_ = clock_.now() + tuning::kBootcacheCooldownTime; } // Checks the clock and calls update if we are off the cooldown. @@ -263,4 +263,4 @@ Bootcache::flagForUpdate() checkUpdate(); } -} // namespace xrpl::PeerFinder +} // namespace xrpl::peer_finder diff --git a/src/libxrpl/peerfinder/Config.cpp b/src/libxrpl/peerfinder/Config.cpp index 3e2f74f42b..60ac0ca547 100644 --- a/src/libxrpl/peerfinder/Config.cpp +++ b/src/libxrpl/peerfinder/Config.cpp @@ -7,13 +7,13 @@ #include #include -namespace xrpl::PeerFinder { +namespace xrpl::peer_finder { std::size_t Config::calcOutPeers() const { return std::max( - ((maxPeers * Tuning::kOutPercent) + 50) / 100, std::size_t(Tuning::kMinOutCount)); + ((maxPeers * tuning::kOutPercent) + 50) / 100, std::size_t(tuning::kMinOutCount)); } void @@ -26,8 +26,8 @@ Config::applyTuning() // IP addresses. ipLimit = 2; - if (inPeers > Tuning::kDefaultMaxPeers) - ipLimit += std::min(5, static_cast(inPeers / Tuning::kDefaultMaxPeers)); + if (inPeers > tuning::kDefaultMaxPeers) + ipLimit += std::min(5, static_cast(inPeers / tuning::kDefaultMaxPeers)); } // We don't allow a single IP to consume all incoming slots, @@ -58,7 +58,7 @@ Config::makeConfig( int ipLimit, bool verifyEndpoints) { - PeerFinder::Config config; + peer_finder::Config config; if (!limits.maxPeers) { @@ -85,7 +85,7 @@ Config::makeConfig( if (limits.maxPeers && *limits.maxPeers != 0) config.maxPeers = *limits.maxPeers; - config.maxPeers = std::max(config.maxPeers, Tuning::kMinOutCount); + config.maxPeers = std::max(config.maxPeers, tuning::kMinOutCount); config.outPeers = config.calcOutPeers(); // Calculate the number of outbound peers we want. If we dont want @@ -132,4 +132,4 @@ Config::makeConfig( return config; } -} // namespace xrpl::PeerFinder +} // namespace xrpl::peer_finder diff --git a/src/libxrpl/peerfinder/Endpoint.cpp b/src/libxrpl/peerfinder/Endpoint.cpp index 12f2725ea5..6f3e2289f9 100644 --- a/src/libxrpl/peerfinder/Endpoint.cpp +++ b/src/libxrpl/peerfinder/Endpoint.cpp @@ -5,11 +5,11 @@ #include #include -namespace xrpl::PeerFinder { +namespace xrpl::peer_finder { -Endpoint::Endpoint(beast::IP::Endpoint ep, std::uint32_t hops) - : hops(std::min(hops, Tuning::kMaxHops + 1)), address(std::move(ep)) +Endpoint::Endpoint(beast::ip::Endpoint ep, std::uint32_t hops) + : hops(std::min(hops, tuning::kMaxHops + 1)), address(std::move(ep)) { } -} // namespace xrpl::PeerFinder +} // namespace xrpl::peer_finder diff --git a/src/libxrpl/peerfinder/PeerfinderManager.cpp b/src/libxrpl/peerfinder/PeerfinderManager.cpp index 2219627f09..0cce2389ec 100644 --- a/src/libxrpl/peerfinder/PeerfinderManager.cpp +++ b/src/libxrpl/peerfinder/PeerfinderManager.cpp @@ -29,7 +29,7 @@ #include #include -namespace xrpl::PeerFinder { +namespace xrpl::peer_finder { class ManagerImp : public Manager { @@ -98,7 +98,7 @@ public: } void - addFixedPeer(std::string_view name, std::vector const& addresses) override + addFixedPeer(std::string_view name, std::vector const& addresses) override { logic_.addFixedPeer(name, addresses); } @@ -119,14 +119,14 @@ public: std::pair, Result> newInboundSlot( - beast::IP::Endpoint const& localEndpoint, - beast::IP::Endpoint const& remoteEndpoint) override + beast::ip::Endpoint const& localEndpoint, + beast::ip::Endpoint const& remoteEndpoint) override { return logic_.newInboundSlot(localEndpoint, remoteEndpoint); } std::pair, Result> - newOutboundSlot(beast::IP::Endpoint const& remoteEndpoint) override + newOutboundSlot(beast::ip::Endpoint const& remoteEndpoint) override { return logic_.newOutboundSlot(remoteEndpoint); } @@ -163,7 +163,7 @@ public: //-------------------------------------------------------------------------- bool - onConnected(std::shared_ptr const& slot, beast::IP::Endpoint const& localEndpoint) + onConnected(std::shared_ptr const& slot, beast::ip::Endpoint const& localEndpoint) override { SlotImp::ptr const impl(std::dynamic_pointer_cast(slot)); @@ -184,7 +184,7 @@ public: return logic_.redirect(impl); } - std::vector + std::vector autoconnect() override { return logic_.autoconnect(); @@ -265,4 +265,4 @@ makeManager( return std::make_unique(ioContext, clock, journal, store, collector); } -} // namespace xrpl::PeerFinder +} // namespace xrpl::peer_finder diff --git a/src/libxrpl/peerfinder/SlotImp.cpp b/src/libxrpl/peerfinder/SlotImp.cpp index 0a4f32fd62..5209bd51ab 100644 --- a/src/libxrpl/peerfinder/SlotImp.cpp +++ b/src/libxrpl/peerfinder/SlotImp.cpp @@ -9,11 +9,11 @@ #include #include -namespace xrpl::PeerFinder { +namespace xrpl::peer_finder { SlotImp::SlotImp( - beast::IP::Endpoint const& localEndpoint, - beast::IP::Endpoint remoteEndpoint, + beast::ip::Endpoint const& localEndpoint, + beast::ip::Endpoint remoteEndpoint, bool fixed, clock_type& clock) : recent(clock) @@ -30,7 +30,7 @@ SlotImp::SlotImp( { } -SlotImp::SlotImp(beast::IP::Endpoint remoteEndpoint, bool fixed, clock_type& clock) +SlotImp::SlotImp(beast::ip::Endpoint remoteEndpoint, bool fixed, clock_type& clock) : recent(clock) , inbound_(false) , fixed_(fixed) @@ -49,29 +49,29 @@ SlotImp::state(State state) { // Must go through activate() to set active state XRPL_ASSERT( - state != State::Active, "xrpl::PeerFinder::SlotImp::state : input state is not active"); + state != State::Active, "xrpl::peer_finder::SlotImp::state : input state is not active"); // The state must be different XRPL_ASSERT( state_ != state, - "xrpl::PeerFinder::SlotImp::state : input state is different from " + "xrpl::peer_finder::SlotImp::state : input state is different from " "current"); // You can't transition into the initial states XRPL_ASSERT( state != State::Accept && state != State::Connect, - "xrpl::PeerFinder::SlotImp::state : input state is not an initial"); + "xrpl::peer_finder::SlotImp::state : input state is not an initial"); // Can only become connected from outbound connect state XRPL_ASSERT( state != State::Connected || (!inbound_ && state_ == State::Connect), - "xrpl::PeerFinder::SlotImp::state : input state is not connected an " + "xrpl::peer_finder::SlotImp::state : input state is not connected an " "invalid state"); // Can't gracefully close on an outbound connection attempt XRPL_ASSERT( state != State::Closing || state_ != State::Connect, - "xrpl::PeerFinder::SlotImp::state : input state is not closing an " + "xrpl::peer_finder::SlotImp::state : input state is not closing an " "invalid state"); state_ = state; @@ -83,7 +83,7 @@ SlotImp::activate(clock_type::time_point const& now) // Can only become active from the accept or connected state XRPL_ASSERT( state_ == State::Accept || state_ == State::Connected, - "xrpl::PeerFinder::SlotImp::activate : valid state"); + "xrpl::peer_finder::SlotImp::activate : valid state"); state_ = State::Active; whenAcceptEndpoints = now; @@ -100,7 +100,7 @@ SlotImp::RecentT::RecentT(clock_type& clock) : cache_(clock) } void -SlotImp::RecentT::insert(beast::IP::Endpoint const& ep, std::uint32_t hops) +SlotImp::RecentT::insert(beast::ip::Endpoint const& ep, std::uint32_t hops) { auto const result(cache_.emplace(ep, hops)); if (!result.second) @@ -115,7 +115,7 @@ SlotImp::RecentT::insert(beast::IP::Endpoint const& ep, std::uint32_t hops) } bool -SlotImp::RecentT::filter(beast::IP::Endpoint const& ep, std::uint32_t hops) +SlotImp::RecentT::filter(beast::ip::Endpoint const& ep, std::uint32_t hops) { auto const iter(cache_.find(ep)); if (iter == cache_.end()) @@ -129,7 +129,7 @@ SlotImp::RecentT::filter(beast::IP::Endpoint const& ep, std::uint32_t hops) void SlotImp::RecentT::expire() { - beast::expire(cache_, Tuning::kLiveCacheSecondsToLive); + beast::expire(cache_, tuning::kLiveCacheSecondsToLive); } -} // namespace xrpl::PeerFinder +} // namespace xrpl::peer_finder diff --git a/src/libxrpl/peerfinder/SourceStrings.cpp b/src/libxrpl/peerfinder/SourceStrings.cpp index f47e0cd51d..ca6ff07cba 100644 --- a/src/libxrpl/peerfinder/SourceStrings.cpp +++ b/src/libxrpl/peerfinder/SourceStrings.cpp @@ -8,7 +8,7 @@ #include #include -namespace xrpl::PeerFinder { +namespace xrpl::peer_finder { class SourceStringsImp : public SourceStrings { @@ -33,9 +33,9 @@ public: results.addresses.reserve(strings_.size()); for (auto const& str : strings_) { - beast::IP::Endpoint ep(beast::IP::Endpoint::fromString(str)); + beast::ip::Endpoint ep(beast::ip::Endpoint::fromString(str)); if (isUnspecified(ep)) - ep = beast::IP::Endpoint::fromString(str); + ep = beast::ip::Endpoint::fromString(str); if (!isUnspecified(ep)) results.addresses.push_back(ep); } @@ -54,4 +54,4 @@ SourceStrings::make(std::string const& name, Strings const& strings) return std::make_shared(name, strings); } -} // namespace xrpl::PeerFinder +} // namespace xrpl::peer_finder diff --git a/src/libxrpl/protocol/BuildInfo.cpp b/src/libxrpl/protocol/BuildInfo.cpp index 6ac352f3e1..8a18b3f228 100644 --- a/src/libxrpl/protocol/BuildInfo.cpp +++ b/src/libxrpl/protocol/BuildInfo.cpp @@ -13,7 +13,7 @@ #include #include -namespace xrpl::BuildInfo { +namespace xrpl::build_info { namespace { @@ -173,4 +173,4 @@ isNewerVersion(std::uint64_t version) return false; } -} // namespace xrpl::BuildInfo +} // namespace xrpl::build_info diff --git a/src/libxrpl/protocol/ErrorCodes.cpp b/src/libxrpl/protocol/ErrorCodes.cpp index 87761ce13e..e81f975844 100644 --- a/src/libxrpl/protocol/ErrorCodes.cpp +++ b/src/libxrpl/protocol/ErrorCodes.cpp @@ -9,7 +9,7 @@ #include namespace xrpl { -namespace RPC { +namespace rpc { namespace detail { @@ -215,12 +215,12 @@ errorCodeHttpStatus(ErrorCodeI code) return getErrorInfo(code).httpStatus; } -} // namespace RPC +} // namespace rpc std::string rpcErrorString(json::Value const& jv) { - XRPL_ASSERT(RPC::containsError(jv), "xrpl::RPC::rpcErrorString : input contains an error"); + XRPL_ASSERT(rpc::containsError(jv), "xrpl::rpc::rpcErrorString : input contains an error"); return jv[jss::error].asString() + jv[jss::error_message].asString(); } diff --git a/src/libxrpl/protocol/NFTSyntheticSerializer.cpp b/src/libxrpl/protocol/NFTSyntheticSerializer.cpp index 4f0a2d5071..fd44ae1f33 100644 --- a/src/libxrpl/protocol/NFTSyntheticSerializer.cpp +++ b/src/libxrpl/protocol/NFTSyntheticSerializer.cpp @@ -9,7 +9,7 @@ #include -namespace xrpl::RPC { +namespace xrpl::rpc { void insertNFTSyntheticInJson( @@ -21,4 +21,4 @@ insertNFTSyntheticInJson( insertNFTokenOfferID(response[jss::meta], transaction, transactionMeta); } -} // namespace xrpl::RPC +} // namespace xrpl::rpc diff --git a/src/libxrpl/protocol/RPCErr.cpp b/src/libxrpl/protocol/RPCErr.cpp index ec9a3dee9d..c172a1898d 100644 --- a/src/libxrpl/protocol/RPCErr.cpp +++ b/src/libxrpl/protocol/RPCErr.cpp @@ -13,7 +13,7 @@ json::Value rpcError(ErrorCodeI iError) { json::Value jvResult(json::ValueType::Object); - RPC::injectError(iError, jvResult); + rpc::injectError(iError, jvResult); return jvResult; } diff --git a/src/libxrpl/protocol/STParsedJSON.cpp b/src/libxrpl/protocol/STParsedJSON.cpp index 33ca5424d1..6ab272b3d1 100644 --- a/src/libxrpl/protocol/STParsedJSON.cpp +++ b/src/libxrpl/protocol/STParsedJSON.cpp @@ -48,7 +48,7 @@ namespace xrpl { -namespace STParsedJSONDetail { +namespace st_parsed_json_detail { template constexpr U toUnsigned(S value) @@ -93,7 +93,7 @@ makeName(std::string const& object, std::string const& field) static inline json::Value notAnObject(std::string const& object, std::string const& field) { - return RPC::makeError( + return rpc::makeError( RpcInvalidParams, "Field '" + makeName(object, field) + "' is not a JSON object."); } @@ -106,33 +106,33 @@ notAnObject(std::string const& object) static inline json::Value notAnArray(std::string const& object) { - return RPC::makeError(RpcInvalidParams, "Field '" + object + "' is not a JSON array."); + return rpc::makeError(RpcInvalidParams, "Field '" + object + "' is not a JSON array."); } static inline json::Value unknownField(std::string const& object, std::string const& field) { - return RPC::makeError(RpcInvalidParams, "Field '" + makeName(object, field) + "' is unknown."); + return rpc::makeError(RpcInvalidParams, "Field '" + makeName(object, field) + "' is unknown."); } static inline json::Value outOfRange(std::string const& object, std::string const& field) { - return RPC::makeError( + return rpc::makeError( RpcInvalidParams, "Field '" + makeName(object, field) + "' is out of range."); } static inline json::Value badType(std::string const& object, std::string const& field) { - return RPC::makeError( + return rpc::makeError( RpcInvalidParams, "Field '" + makeName(object, field) + "' has bad type."); } static inline json::Value invalidData(std::string const& object, std::string const& field) { - return RPC::makeError( + return rpc::makeError( RpcInvalidParams, "Field '" + makeName(object, field) + "' has invalid data."); } @@ -145,14 +145,14 @@ invalidData(std::string const& object) static inline json::Value arrayExpected(std::string const& object, std::string const& field) { - return RPC::makeError( + return rpc::makeError( RpcInvalidParams, "Field '" + makeName(object, field) + "' must be a JSON array."); } static inline json::Value arrayTooBig(std::string const& object, std::string const& field) { - return RPC::makeError( + return rpc::makeError( RpcInvalidParams, "Field '" + makeName(object, field) + "' exceeds allowed JSON array size of " + std::to_string(kMaxParsedJsonArraySize) + " elements per field."); @@ -161,20 +161,20 @@ arrayTooBig(std::string const& object, std::string const& field) static inline json::Value stringExpected(std::string const& object, std::string const& field) { - return RPC::makeError( + return rpc::makeError( RpcInvalidParams, "Field '" + makeName(object, field) + "' must be a string."); } static inline json::Value tooDeep(std::string const& object) { - return RPC::makeError(RpcInvalidParams, "Field '" + object + "' exceeds nesting depth limit."); + return rpc::makeError(RpcInvalidParams, "Field '" + object + "' exceeds nesting depth limit."); } static inline json::Value singletonExpected(std::string const& object, unsigned int index) { - return RPC::makeError( + return rpc::makeError( RpcInvalidParams, "Field '" + object + "[" + std::to_string(index) + "]' must be an object with a single key/object value."); @@ -183,7 +183,7 @@ singletonExpected(std::string const& object, unsigned int index) static inline json::Value templateMismatch(SField const& sField) { - return RPC::makeError( + return rpc::makeError( RpcInvalidParams, "Object '" + sField.getName() + "' contents did not meet requirements for that type."); } @@ -191,7 +191,7 @@ templateMismatch(SField const& sField) static inline json::Value nonObjectInArray(std::string const& item, json::UInt index) { - return RPC::makeError( + return rpc::makeError( RpcInvalidParams, "Item '" + item + "' at index " + std::to_string(index) + " is not an object. Arrays may only contain objects."); @@ -791,7 +791,7 @@ parseLeaf( if (pathEl.isMember(jss::currency) && pathEl.isMember(jss::mpt_issuance_id)) { - error = RPC::makeError(RpcInvalidParams, "Invalid Asset."); + error = rpc::makeError(RpcInvalidParams, "Invalid Asset."); return ret; } @@ -1195,13 +1195,13 @@ parseArray( } } -} // namespace STParsedJSONDetail +} // namespace st_parsed_json_detail //------------------------------------------------------------------------------ STParsedJSONObject::STParsedJSONObject(std::string const& name, json::Value const& json) { - using namespace STParsedJSONDetail; + using namespace st_parsed_json_detail; object = parseObject(name, json, sfGeneric, 0, error); } diff --git a/src/libxrpl/protocol/XChainAttestations.cpp b/src/libxrpl/protocol/XChainAttestations.cpp index 792fe5da9d..7c887e785b 100644 --- a/src/libxrpl/protocol/XChainAttestations.cpp +++ b/src/libxrpl/protocol/XChainAttestations.cpp @@ -24,7 +24,7 @@ #include namespace xrpl { -namespace Attestations { +namespace attestations { AttestationBase::AttestationBase( AccountID attestationSignerAccount, @@ -385,7 +385,7 @@ operator==(AttestationCreateAccount const& lhs, AttestationCreateAccount const& std::tie(rhs.createCount, rhs.toCreate, rhs.rewardAmount); } -} // namespace Attestations +} // namespace attestations SField const& XChainClaimAttestation::arrayFieldName{sfXChainClaimAttestations}; SField const& XChainCreateAccountAttestation::arrayFieldName{sfXChainCreateAccountAttestations}; diff --git a/src/libxrpl/resource/Charge.cpp b/src/libxrpl/resource/Charge.cpp index e174c13522..f80588b143 100644 --- a/src/libxrpl/resource/Charge.cpp +++ b/src/libxrpl/resource/Charge.cpp @@ -6,7 +6,7 @@ #include #include -namespace xrpl::Resource { +namespace xrpl::resource { Charge::Charge(value_type cost, std::string label) : cost_(cost), label_(std::move(label)) { @@ -57,4 +57,4 @@ Charge::operator*(value_type m) const return Charge(cost_ * m, label_); } -} // namespace xrpl::Resource +} // namespace xrpl::resource diff --git a/src/libxrpl/resource/Consumer.cpp b/src/libxrpl/resource/Consumer.cpp index 58d5775a31..934aaddbf5 100644 --- a/src/libxrpl/resource/Consumer.cpp +++ b/src/libxrpl/resource/Consumer.cpp @@ -12,7 +12,7 @@ #include #include -namespace xrpl::Resource { +namespace xrpl::resource { Consumer::Consumer(Logic& logic, Entry& entry) : logic_(&logic), entry_(&entry) { @@ -99,14 +99,14 @@ Consumer::charge(Charge const& what, std::string const& context) bool Consumer::warn() { - XRPL_ASSERT(entry_, "xrpl::Resource::Consumer::warn : non-null entry"); + XRPL_ASSERT(entry_, "xrpl::resource::Consumer::warn : non-null entry"); return logic_->warn(*entry_); } bool Consumer::disconnect(beast::Journal const& j) { - XRPL_ASSERT(entry_, "xrpl::Resource::Consumer::disconnect : non-null entry"); + XRPL_ASSERT(entry_, "xrpl::resource::Consumer::disconnect : non-null entry"); bool const d = logic_->disconnect(*entry_); if (d) { @@ -118,14 +118,14 @@ Consumer::disconnect(beast::Journal const& j) int Consumer::balance() { - XRPL_ASSERT(entry_, "xrpl::Resource::Consumer::balance : non-null entry"); + XRPL_ASSERT(entry_, "xrpl::resource::Consumer::balance : non-null entry"); return logic_->balance(*entry_); } Entry& Consumer::entry() { - XRPL_ASSERT(entry_, "xrpl::Resource::Consumer::entry : non-null entry"); + XRPL_ASSERT(entry_, "xrpl::resource::Consumer::entry : non-null entry"); return *entry_; } @@ -142,4 +142,4 @@ operator<<(std::ostream& os, Consumer const& v) return os; } -} // namespace xrpl::Resource +} // namespace xrpl::resource diff --git a/src/libxrpl/resource/Fees.cpp b/src/libxrpl/resource/Fees.cpp index bb825fa3c7..037f60051e 100644 --- a/src/libxrpl/resource/Fees.cpp +++ b/src/libxrpl/resource/Fees.cpp @@ -2,7 +2,7 @@ #include -namespace xrpl::Resource { +namespace xrpl::resource { Charge const kFeeMalformedRequest(200, "malformed request"); Charge const kFeeRequestNoReply(10, "unsatisfiable request"); @@ -23,6 +23,6 @@ Charge const kFeeHeavyBurdenPeer(2000, "heavy peer request"); Charge const kFeeWarning(4000, "received warning"); Charge const kFeeDrop(6000, "dropped"); -// See also Resource::Logic::charge for log level cutoff values +// See also resource::Logic::charge for log level cutoff values -} // namespace xrpl::Resource +} // namespace xrpl::resource diff --git a/src/libxrpl/resource/ResourceManager.cpp b/src/libxrpl/resource/ResourceManager.cpp index e3b4d9cc5c..cdfa95facd 100644 --- a/src/libxrpl/resource/ResourceManager.cpp +++ b/src/libxrpl/resource/ResourceManager.cpp @@ -23,7 +23,7 @@ #include #include -namespace xrpl::Resource { +namespace xrpl::resource { class ManagerImp : public Manager { @@ -58,14 +58,14 @@ public: } Consumer - newInboundEndpoint(beast::IP::Endpoint const& address) override + newInboundEndpoint(beast::ip::Endpoint const& address) override { return logic_.newInboundEndpoint(address); } Consumer newInboundEndpoint( - beast::IP::Endpoint const& address, + beast::ip::Endpoint const& address, bool const proxy, std::string_view forwardedFor) override { @@ -85,13 +85,13 @@ public: } Consumer - newOutboundEndpoint(beast::IP::Endpoint const& address) override + newOutboundEndpoint(beast::ip::Endpoint const& address) override { return logic_.newOutboundEndpoint(address); } Consumer - newUnlimitedEndpoint(beast::IP::Endpoint const& address) override + newUnlimitedEndpoint(beast::ip::Endpoint const& address) override { return logic_.newUnlimitedEndpoint(address); } @@ -136,7 +136,7 @@ private: void run() { - beast::setCurrentThreadName("Resource::Mngr"); + beast::setCurrentThreadName("resource::Mngr"); for (;;) { logic_.periodicActivity(); @@ -164,4 +164,4 @@ makeManager(beast::insight::Collector::ptr const& collector, beast::Journal jour return std::make_unique(collector, journal); } -} // namespace xrpl::Resource +} // namespace xrpl::resource diff --git a/src/libxrpl/server/InfoSub.cpp b/src/libxrpl/server/InfoSub.cpp index 353c295856..39883873fb 100644 --- a/src/libxrpl/server/InfoSub.cpp +++ b/src/libxrpl/server/InfoSub.cpp @@ -104,7 +104,7 @@ InfoSub::~InfoSub() } } -Resource::Consumer& +resource::Consumer& InfoSub::getConsumer() { return consumer_; diff --git a/src/libxrpl/server/JSONRPCUtil.cpp b/src/libxrpl/server/JSONRPCUtil.cpp index f38ff280ac..d59582fc1a 100644 --- a/src/libxrpl/server/JSONRPCUtil.cpp +++ b/src/libxrpl/server/JSONRPCUtil.cpp @@ -42,7 +42,7 @@ httpReply(int nStatus, std::string const& content, json::Output const& output, b // CHECKME this returns a different version than the replies below. Is // this by design or an accident or should it be using - // BuildInfo::getFullVersionString () as well? + // build_info::getFullVersionString () as well? output("Server: " + systemName() + "-json-rpc/v1"); output("\r\n"); @@ -123,7 +123,7 @@ httpReply(int nStatus, std::string const& content, json::Output const& output, b "Content-Type: application/json; charset=UTF-8\r\n"); output("Server: " + systemName() + "-json-rpc/"); - output(BuildInfo::getFullVersionString()); + output(build_info::getFullVersionString()); output( "\r\n" "\r\n"); diff --git a/src/libxrpl/server/Port.cpp b/src/libxrpl/server/Port.cpp index c1a79019af..694d4448d5 100644 --- a/src/libxrpl/server/Port.cpp +++ b/src/libxrpl/server/Port.cpp @@ -107,7 +107,7 @@ populate( { // First, check to see if 0.0.0.0 or ipv6 equivalent was configured, // which means all IP addresses. - auto const addr = beast::IP::Endpoint::fromStringChecked(ip); + auto const addr = beast::ip::Endpoint::fromStringChecked(ip); if (addr) { if (isUnspecified(*addr)) diff --git a/src/libxrpl/tx/transactors/bridge/XChainBridge.cpp b/src/libxrpl/tx/transactors/bridge/XChainBridge.cpp index e03cb56fd5..cbfb93c386 100644 --- a/src/libxrpl/tx/transactors/bridge/XChainBridge.cpp +++ b/src/libxrpl/tx/transactors/bridge/XChainBridge.cpp @@ -864,7 +864,7 @@ applyClaimAttestations( return std::unexpected(tecXCHAIN_NO_CLAIM_ID); // Add claims that are part of the signer's list to the "claims" vector - std::vector atts; + std::vector atts; atts.reserve(std::distance(attBegin, attEnd)); for (auto att = attBegin; att != attEnd; ++att) { @@ -1042,7 +1042,7 @@ applyCreateAccountAttestations( return std::unexpected(tecINSUFFICIENT_RESERVE); } - std::vector atts; + std::vector atts; atts.reserve(std::distance(attBegin, attEnd)); for (auto att = attBegin; att != attEnd; ++att) { @@ -1160,8 +1160,8 @@ std::optional toClaim(STTx const& tx) { static_assert( - std::is_same_v || - std::is_same_v); + std::is_same_v || + std::is_same_v); try { @@ -1301,10 +1301,10 @@ attestationDoApply(ApplyContext& ctx) auto const& [srcChain, signersList, quorum, thisDoor, bridgeK] = scopeResult.value(); static_assert( - std::is_same_v || - std::is_same_v); + std::is_same_v || + std::is_same_v); - if constexpr (std::is_same_v) + if constexpr (std::is_same_v) { return applyClaimAttestations( ctx.view(), @@ -1317,7 +1317,7 @@ attestationDoApply(ApplyContext& ctx) quorum, ctx.journal); } - else if constexpr (std::is_same_v) + else if constexpr (std::is_same_v) { return applyCreateAccountAttestations( ctx.view(), @@ -2067,19 +2067,19 @@ XChainCreateClaimID::doApply() NotTEC XChainAddClaimAttestation::preflight(PreflightContext const& ctx) { - return attestationPreflight(ctx); + return attestationPreflight(ctx); } TER XChainAddClaimAttestation::preclaim(PreclaimContext const& ctx) { - return attestationPreclaim(ctx); + return attestationPreclaim(ctx); } TER XChainAddClaimAttestation::doApply() { - return attestationDoApply(ctx_); + return attestationDoApply(ctx_); } //------------------------------------------------------------------------------ @@ -2087,19 +2087,19 @@ XChainAddClaimAttestation::doApply() NotTEC XChainAddAccountCreateAttestation::preflight(PreflightContext const& ctx) { - return attestationPreflight(ctx); + return attestationPreflight(ctx); } TER XChainAddAccountCreateAttestation::preclaim(PreclaimContext const& ctx) { - return attestationPreclaim(ctx); + return attestationPreclaim(ctx); } TER XChainAddAccountCreateAttestation::doApply() { - return attestationDoApply(ctx_); + return attestationDoApply(ctx_); } //------------------------------------------------------------------------------ diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerSet.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerSet.cpp index e9c153404c..b12cfb692f 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerSet.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerSet.cpp @@ -34,7 +34,7 @@ LoanBrokerSet::checkExtraFeatures(PreflightContext const& ctx) NotTEC LoanBrokerSet::preflight(PreflightContext const& ctx) { - using namespace Lending; + using namespace lending; auto const& tx = ctx.tx; if (auto const data = tx[~sfData]; diff --git a/src/libxrpl/tx/transactors/lending/LoanPay.cpp b/src/libxrpl/tx/transactors/lending/LoanPay.cpp index 0053ed496e..74e8efeda2 100644 --- a/src/libxrpl/tx/transactors/lending/LoanPay.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanPay.cpp @@ -73,7 +73,7 @@ LoanPay::preflight(PreflightContext const& ctx) XRPAmount LoanPay::calculateBaseFee(ReadView const& view, STTx const& tx) { - using namespace Lending; + using namespace lending; auto const normalCost = Transactor::calculateBaseFee(view, tx); diff --git a/src/libxrpl/tx/transactors/lending/LoanSet.cpp b/src/libxrpl/tx/transactors/lending/LoanSet.cpp index bafadd7c1d..95a9581dd3 100644 --- a/src/libxrpl/tx/transactors/lending/LoanSet.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanSet.cpp @@ -53,7 +53,7 @@ LoanSet::getFlagsMask(PreflightContext const& ctx) NotTEC LoanSet::preflight(PreflightContext const& ctx) { - using namespace Lending; + using namespace lending; auto const& tx = ctx.tx; diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index ffaf26b5a7..5230a1f7dd 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -3179,7 +3179,7 @@ class Batch_test : public beast::unit_test::Suite auto const brokerKeylet = keylet::loanBroker(lender.id(), env.seq(lender)); { - using namespace loanBroker; + using namespace loan_broker; env(set(lender, vaultKeylet.key), kManagementFeeRate(TenthBips16(100)), kDebtMaximum(debtMaximumValue), diff --git a/src/test/app/Delegate_test.cpp b/src/test/app/Delegate_test.cpp index 257ed33619..788514e284 100644 --- a/src/test/app/Delegate_test.cpp +++ b/src/test/app/Delegate_test.cpp @@ -2823,15 +2823,15 @@ class Delegate_test : public beast::unit_test::Suite auto [createTx, keylet] = vault.create({.owner = alice, .asset = xrpIssue()}); env(createTx); - env(loanBroker::set(alice, keylet.key), delegate::As(bob), Ter(temINVALID)); - env(loanBroker::del(alice, keylet.key), delegate::As(bob), Ter(temINVALID)); - env(loanBroker::coverDeposit(alice, keylet.key, XRP(1)), + env(loan_broker::set(alice, keylet.key), delegate::As(bob), Ter(temINVALID)); + env(loan_broker::del(alice, keylet.key), delegate::As(bob), Ter(temINVALID)); + env(loan_broker::coverDeposit(alice, keylet.key, XRP(1)), delegate::As(bob), Ter(temINVALID)); - env(loanBroker::coverWithdraw(alice, keylet.key, XRP(1)), + env(loan_broker::coverWithdraw(alice, keylet.key, XRP(1)), delegate::As(bob), Ter(temINVALID)); - env(loanBroker::coverClawback(alice), delegate::As(bob), Ter(temINVALID)); + env(loan_broker::coverClawback(alice), delegate::As(bob), Ter(temINVALID)); env(loan::set(alice, keylet.key, Number(100)), delegate::As(bob), Ter(temINVALID)); env(loan::manage(alice, keylet.key, 0), delegate::As(bob), Ter(temINVALID)); diff --git a/src/test/app/FixNFTokenPageLinks_test.cpp b/src/test/app/FixNFTokenPageLinks_test.cpp index 7b13fc060b..9be01b2abe 100644 --- a/src/test/app/FixNFTokenPageLinks_test.cpp +++ b/src/test/app/FixNFTokenPageLinks_test.cpp @@ -139,7 +139,7 @@ class FixNFTokenPageLinks_test : public beast::unit_test::Suite env.fund(XRP(1000), alice); auto const linkFixFee = drops(env.current()->fees().increment); - env(ledgerStateFix::nftPageLinks(alice, alice), Fee(linkFixFee), Ter(temDISABLED)); + env(ledger_state_fix::nftPageLinks(alice, alice), Fee(linkFixFee), Ter(temDISABLED)); } Env env{*this, testableAmendments()}; @@ -151,38 +151,38 @@ class FixNFTokenPageLinks_test : public beast::unit_test::Suite { // Fail preflight1. Can't combine AccountTxnID and ticket. - json::Value tx = ledgerStateFix::nftPageLinks(alice, alice); + json::Value tx = ledger_state_fix::nftPageLinks(alice, alice); tx[sfAccountTxnID.jsonName] = "00000000000000000000000000000000" "00000000000000000000000000000000"; env(tx, ticket::Use(ticketSeq), Ter(temINVALID)); } // Fee too low. - env(ledgerStateFix::nftPageLinks(alice, alice), Ter(telINSUF_FEE_P)); + env(ledger_state_fix::nftPageLinks(alice, alice), Ter(telINSUF_FEE_P)); // Invalid flags. auto const linkFixFee = drops(env.current()->fees().increment); - env(ledgerStateFix::nftPageLinks(alice, alice), + env(ledger_state_fix::nftPageLinks(alice, alice), Fee(linkFixFee), Txflags(tfPassive), Ter(temINVALID_FLAG)); { - // ledgerStateFix::nftPageLinks requires an Owner field. - json::Value tx = ledgerStateFix::nftPageLinks(alice, alice); + // ledger_state_fix::nftPageLinks requires an Owner field. + json::Value tx = ledger_state_fix::nftPageLinks(alice, alice); tx.removeMember(sfOwner.jsonName); env(tx, Fee(linkFixFee), Ter(temINVALID)); } { // NFTokenPageLink fixes require sfOwner and reject fields that // belong to other LedgerStateFix types. - json::Value tx = ledgerStateFix::nftPageLinks(alice, alice); + json::Value tx = ledger_state_fix::nftPageLinks(alice, alice); tx[sfBookDirectory.jsonName] = to_string(uint256{1}); env(tx, Fee(linkFixFee), Ter(temINVALID)); } { // Invalid LedgerFixType codes. - json::Value tx = ledgerStateFix::nftPageLinks(alice, alice); + json::Value tx = ledger_state_fix::nftPageLinks(alice, alice); tx[sfLedgerFixType.jsonName] = 0; env(tx, Fee(linkFixFee), Ter(tefINVALID_LEDGER_FIX_TYPE)); @@ -193,7 +193,9 @@ class FixNFTokenPageLinks_test : public beast::unit_test::Suite // Preclaim Account const carol("carol"); env.memoize(carol); - env(ledgerStateFix::nftPageLinks(alice, carol), Fee(linkFixFee), Ter(tecOBJECT_NOT_FOUND)); + env(ledger_state_fix::nftPageLinks(alice, carol), + Fee(linkFixFee), + Ter(tecOBJECT_NOT_FOUND)); } void @@ -214,13 +216,17 @@ class FixNFTokenPageLinks_test : public beast::unit_test::Suite // Owner has no pages to fix. auto const linkFixFee = drops(env.current()->fees().increment); - env(ledgerStateFix::nftPageLinks(alice, alice), Fee(linkFixFee), Ter(tecFAILED_PROCESSING)); + env(ledger_state_fix::nftPageLinks(alice, alice), + Fee(linkFixFee), + Ter(tecFAILED_PROCESSING)); // Alice has only one page. env(token::mint(alice), Txflags(tfTransferable)); env.close(); - env(ledgerStateFix::nftPageLinks(alice, alice), Fee(linkFixFee), Ter(tecFAILED_PROCESSING)); + env(ledger_state_fix::nftPageLinks(alice, alice), + Fee(linkFixFee), + Ter(tecFAILED_PROCESSING)); // Alice has at least three pages. for (std::uint32_t i = 0; i < 64; ++i) @@ -229,7 +235,9 @@ class FixNFTokenPageLinks_test : public beast::unit_test::Suite env.close(); } - env(ledgerStateFix::nftPageLinks(alice, alice), Fee(linkFixFee), Ter(tecFAILED_PROCESSING)); + env(ledger_state_fix::nftPageLinks(alice, alice), + Fee(linkFixFee), + Ter(tecFAILED_PROCESSING)); } void @@ -439,7 +447,7 @@ class FixNFTokenPageLinks_test : public beast::unit_test::Suite //********************************************************************** // Verify that the LedgerStateFix transaction is not enabled. auto const linkFixFee = drops(env.current()->fees().increment); - env(ledgerStateFix::nftPageLinks(daria, alice), Fee(linkFixFee), Ter(temDISABLED)); + env(ledger_state_fix::nftPageLinks(daria, alice), Fee(linkFixFee), Ter(temDISABLED)); // Wait 15 ledgers so the LedgerStateFix transaction is no longer // retried. @@ -475,7 +483,7 @@ class FixNFTokenPageLinks_test : public beast::unit_test::Suite env(noop(daria)); // daria fixes the links in alice's NFToken directory. - env(ledgerStateFix::nftPageLinks(daria, alice), Fee(linkFixFee)); + env(ledger_state_fix::nftPageLinks(daria, alice), Fee(linkFixFee)); env.close(); // alice's last page should now be present and include no links. @@ -516,7 +524,7 @@ class FixNFTokenPageLinks_test : public beast::unit_test::Suite } // daria fixes the links in bob's NFToken directory. - env(ledgerStateFix::nftPageLinks(daria, bob), Fee(linkFixFee)); + env(ledger_state_fix::nftPageLinks(daria, bob), Fee(linkFixFee)); env.close(); // bob's last page should now be present and include a previous @@ -574,7 +582,7 @@ class FixNFTokenPageLinks_test : public beast::unit_test::Suite } // carol fixes the links in their own NFToken directory. - env(ledgerStateFix::nftPageLinks(carol, carol), Fee(linkFixFee)); + env(ledger_state_fix::nftPageLinks(carol, carol), Fee(linkFixFee)); env.close(); { diff --git a/src/test/app/Invariants_test.cpp b/src/test/app/Invariants_test.cpp index ac6d8e068f..9bc9524f80 100644 --- a/src/test/app/Invariants_test.cpp +++ b/src/test/app/Invariants_test.cpp @@ -2412,7 +2412,7 @@ class Invariants_test : public beast::unit_test::Suite vaultID = vKeylet.key; // Create Loan Broker - using namespace loanBroker; + using namespace loan_broker; auto const loanBrokerKeylet = keylet::loanBroker(a.id(), env.seq(a)); // Create a Loan Broker with all default values. @@ -2721,7 +2721,7 @@ class Invariants_test : public beast::unit_test::Suite brokerKeylet = this->createLoanBroker(alice, env, asset); if (!BEAST_EXPECT(env.le(brokerKeylet))) return false; - env(loanBroker::coverDeposit(alice, brokerKeylet.key, asset(10))); + env(loan_broker::coverDeposit(alice, brokerKeylet.key, asset(10))); env.close(); return BEAST_EXPECT(env.le(brokerKeylet)); }; diff --git a/src/test/app/LedgerReplay_test.cpp b/src/test/app/LedgerReplay_test.cpp index 4cc83608d6..7b521402a4 100644 --- a/src/test/app/LedgerReplay_test.cpp +++ b/src/test/app/LedgerReplay_test.cpp @@ -280,13 +280,13 @@ public: send(std::shared_ptr const& m) override { } - [[nodiscard]] beast::IP::Endpoint + [[nodiscard]] beast::ip::Endpoint getRemoteAddress() const override { return {}; } void - charge(Resource::Charge const& fee, std::string const& context = {}) override + charge(resource::Charge const& fee, std::string const& context = {}) override { } [[nodiscard]] id_t @@ -1206,7 +1206,7 @@ struct LedgerReplayer_test : public beast::unit_test::Suite if (serverResult != expecting) return false; - beast::IP::Address const addr = boost::asio::ip::make_address("172.1.1.100"); + beast::ip::Address const addr = boost::asio::ip::make_address("172.1.1.100"); jtx::Env serverEnv(*this); serverEnv.app().config().ledgerReplay = server; auto httpResp = xrpl::makeResponse( diff --git a/src/test/app/LendingHelpers_test.cpp b/src/test/app/LendingHelpers_test.cpp index 1235920fab..5d67cdc3c5 100644 --- a/src/test/app/LendingHelpers_test.cpp +++ b/src/test/app/LendingHelpers_test.cpp @@ -1475,7 +1475,7 @@ class LendingHelpers_test : public beast::unit_test::Suite void testAccrualLoanOriginationDeltas() { - using namespace xrpl::Accrual; + using namespace xrpl::accrual; struct TestCase { @@ -1495,7 +1495,7 @@ class LendingHelpers_test : public beast::unit_test::Suite for (auto const& tc : testCases) { - testcase("Accrual::loanOriginationDeltas: " + tc.name); + testcase("accrual::loanOriginationDeltas: " + tc.name); auto const deltas = loanOriginationDeltas(tc.principalRequested, tc.interestDue); BEAST_EXPECTS( @@ -1513,9 +1513,9 @@ class LendingHelpers_test : public beast::unit_test::Suite void testCashBasisLoanOriginationDeltas() { - using namespace xrpl::CashBasis; + using namespace xrpl::cash_basis; - testcase("CashBasis::loanOriginationDeltas: interestDue is ignored"); + testcase("cash_basis::loanOriginationDeltas: interestDue is ignored"); Number const principalRequested{1'000}; Number const interestDue{75}; @@ -1533,7 +1533,7 @@ class LendingHelpers_test : public beast::unit_test::Suite void testAccrualLoanOriginationExceedsVaultMaximum() { - using namespace xrpl::Accrual; + using namespace xrpl::accrual; struct TestCase { @@ -1569,7 +1569,7 @@ class LendingHelpers_test : public beast::unit_test::Suite for (auto const& tc : testCases) { - testcase("Accrual::loanOriginationExceedsVaultMaximum: " + tc.name); + testcase("accrual::loanOriginationExceedsVaultMaximum: " + tc.name); BEAST_EXPECT( loanOriginationExceedsVaultMaximum( tc.vaultMaximum, tc.vaultTotal, tc.interestDue) == tc.expected); @@ -1613,19 +1613,19 @@ class LendingHelpers_test : public beast::unit_test::Suite void testAccrualLoanVaultExposure() { - testcase("Accrual::loanVaultExposure"); + testcase("accrual::loanVaultExposure"); auto sle = makeLoanSle(Number{1'000}, Number{800}, Number{50}); - BEAST_EXPECT(xrpl::Accrual::loanVaultExposure(sle) == Number{950}); + BEAST_EXPECT(xrpl::accrual::loanVaultExposure(sle) == Number{950}); } void testCashBasisLoanVaultExposure() { - testcase("CashBasis::loanVaultExposure"); + testcase("cash_basis::loanVaultExposure"); auto sle = makeLoanSle(Number{1'000}, Number{800}, Number{50}); - BEAST_EXPECT(xrpl::CashBasis::loanVaultExposure(sle) == Number{800}); + BEAST_EXPECT(xrpl::cash_basis::loanVaultExposure(sle) == Number{800}); } void @@ -1641,8 +1641,8 @@ class LendingHelpers_test : public beast::unit_test::Suite .feePaid = Number{3}}; { - testcase("Accrual::loanPaymentDeltas: nonzero valueChange"); - auto const deltas = xrpl::Accrual::loanPaymentDeltas(parts); + testcase("accrual::loanPaymentDeltas: nonzero valueChange"); + auto const deltas = xrpl::accrual::loanPaymentDeltas(parts); BEAST_EXPECT(deltas.assetsTotalDelta == parts.valueChange); BEAST_EXPECT( deltas.debtTotalDelta == @@ -1650,8 +1650,8 @@ class LendingHelpers_test : public beast::unit_test::Suite } { - testcase("CashBasis::loanPaymentDeltas: nonzero valueChange ignored"); - auto const deltas = xrpl::CashBasis::loanPaymentDeltas(parts); + testcase("cash_basis::loanPaymentDeltas: nonzero valueChange ignored"); + auto const deltas = xrpl::cash_basis::loanPaymentDeltas(parts); BEAST_EXPECT(deltas.assetsTotalDelta == parts.interestPaid); BEAST_EXPECT(deltas.debtTotalDelta == parts.principalPaid); } @@ -1675,7 +1675,7 @@ class LendingHelpers_test : public beast::unit_test::Suite Env const env{*this}; auto const deltas = loanOriginationDeltas(legacyVault, principalRequested, interestDue); auto const expected = - xrpl::Accrual::loanOriginationDeltas(principalRequested, interestDue); + xrpl::accrual::loanOriginationDeltas(principalRequested, interestDue); BEAST_EXPECT(deltas.assetsTotalDelta == expected.assetsTotalDelta); BEAST_EXPECT(deltas.debtTotalDelta == expected.debtTotalDelta); } @@ -1687,7 +1687,7 @@ class LendingHelpers_test : public beast::unit_test::Suite Env const env{*this}; auto const deltas = loanOriginationDeltas(cashBasisVault, principalRequested, interestDue); - auto const expected = xrpl::CashBasis::loanOriginationDeltas(principalRequested); + auto const expected = xrpl::cash_basis::loanOriginationDeltas(principalRequested); BEAST_EXPECT(deltas.assetsTotalDelta == expected.assetsTotalDelta); BEAST_EXPECT(deltas.debtTotalDelta == expected.debtTotalDelta); } @@ -1713,7 +1713,7 @@ class LendingHelpers_test : public beast::unit_test::Suite Env const env{*this}; BEAST_EXPECT( loanOriginationExceedsVaultMaximum(legacyVault, vaultTotal, interestDue) == - xrpl::Accrual::loanOriginationExceedsVaultMaximum( + xrpl::accrual::loanOriginationExceedsVaultMaximum( vaultMaximum, vaultTotal, interestDue)); } @@ -1741,7 +1741,7 @@ class LendingHelpers_test : public beast::unit_test::Suite Env const env{*this}; auto sle = makeLoanSle(Number{1'000}, Number{800}, Number{50}); BEAST_EXPECT( - loanVaultExposure(legacyVault, sle) == xrpl::Accrual::loanVaultExposure(sle)); + loanVaultExposure(legacyVault, sle) == xrpl::accrual::loanVaultExposure(sle)); } { @@ -1752,7 +1752,7 @@ class LendingHelpers_test : public beast::unit_test::Suite Env const env{*this}; auto sle = makeLoanSle(Number{1'000}, Number{800}, Number{50}); BEAST_EXPECT( - loanVaultExposure(cashBasisVault, sle) == xrpl::CashBasis::loanVaultExposure(sle)); + loanVaultExposure(cashBasisVault, sle) == xrpl::cash_basis::loanVaultExposure(sle)); } } @@ -1774,7 +1774,7 @@ class LendingHelpers_test : public beast::unit_test::Suite testcase("loanPaymentDeltas dispatcher: amendment enabled, legacy vault picks Accrual"); Env const env{*this}; auto const deltas = loanPaymentDeltas(legacyVault, parts); - auto const expected = xrpl::Accrual::loanPaymentDeltas(parts); + auto const expected = xrpl::accrual::loanPaymentDeltas(parts); BEAST_EXPECT(deltas.assetsTotalDelta == expected.assetsTotalDelta); BEAST_EXPECT(deltas.debtTotalDelta == expected.debtTotalDelta); } @@ -1786,7 +1786,7 @@ class LendingHelpers_test : public beast::unit_test::Suite "picks CashBasis"); Env const env{*this}; auto const deltas = loanPaymentDeltas(cashBasisVault, parts); - auto const expected = xrpl::CashBasis::loanPaymentDeltas(parts); + auto const expected = xrpl::cash_basis::loanPaymentDeltas(parts); BEAST_EXPECT(deltas.assetsTotalDelta == expected.assetsTotalDelta); BEAST_EXPECT(deltas.debtTotalDelta == expected.debtTotalDelta); } diff --git a/src/test/app/LoanBroker_test.cpp b/src/test/app/LoanBroker_test.cpp index f6f85a0cca..ee398bfc3b 100644 --- a/src/test/app/LoanBroker_test.cpp +++ b/src/test/app/LoanBroker_test.cpp @@ -93,7 +93,7 @@ class LoanBroker_test : public beast::unit_test::Suite env.close(); BEAST_EXPECT(static_cast(env.le(keylet)) == goodVault); - using namespace loanBroker; + using namespace loan_broker; // Can't create a loan broker regardless of whether the vault exists env(set(alice, keylet.key), Ter(temDISABLED)); auto const brokerKeylet = keylet::loanBroker(alice.id(), env.seq(alice)); @@ -168,7 +168,7 @@ class LoanBroker_test : public beast::unit_test::Suite } using namespace jtx; - using namespace loanBroker; + using namespace loan_broker; // Bogus assets to use in test cases static PrettyAsset const kBadMptAsset = [&]() { @@ -645,8 +645,8 @@ class LoanBroker_test : public beast::unit_test::Suite } } - using namespace loanBroker; - using namespace xrpl::Lending; + using namespace loan_broker; + using namespace xrpl::lending; TenthBips32 const tenthBipsZero{0}; @@ -862,7 +862,7 @@ class LoanBroker_test : public beast::unit_test::Suite LoanBrokerTest brokerTest) { using namespace jtx; - using namespace loanBroker; + using namespace loan_broker; Account const issuer{"issuer"}; Account const alice{"alice"}; Env env(*this); @@ -1093,7 +1093,7 @@ class LoanBroker_test : public beast::unit_test::Suite { testcase("Invalid LoanBrokerCoverClawback"); using namespace jtx; - using namespace loanBroker; + using namespace loan_broker; // preflight { @@ -1220,7 +1220,7 @@ class LoanBroker_test : public beast::unit_test::Suite auto const brokerKeylet = keylet::loanBroker(alice.id(), env.seq(alice)); // Create LoanBroker pointing to the vault - env(loanBroker::set(alice, vaultKeylet.key)); + env(loan_broker::set(alice, vaultKeylet.key)); env.close(); // Build the CoverDeposit STTx directly @@ -1256,7 +1256,7 @@ class LoanBroker_test : public beast::unit_test::Suite { testcase("Require Auth - Implicit Pseudo-account authorization"); using namespace jtx; - using namespace loanBroker; + using namespace loan_broker; Account const issuer{"issuer"}; Account const alice{"alice"}; @@ -1364,7 +1364,7 @@ class LoanBroker_test : public beast::unit_test::Suite { testcase("testLoanBrokerSetDebtMaximum"); using namespace jtx; - using namespace loanBroker; + using namespace loan_broker; Account const issuer{"issuer"}; Account const alice{"alice"}; Env env(*this); @@ -1550,10 +1550,10 @@ class LoanBroker_test : public beast::unit_test::Suite auto const brokerKeylet = keylet::loanBroker(broker, env.seq(broker)); - env(loanBroker::set(broker, keylet.key)); + env(loan_broker::set(broker, keylet.key)); env.close(); - env(loanBroker::coverDeposit(broker, brokerKeylet.key, deposit), Ter(err)); + env(loan_broker::coverDeposit(broker, brokerKeylet.key, deposit), Ter(err)); env.close(); }; @@ -1621,7 +1621,7 @@ class LoanBroker_test : public beast::unit_test::Suite auto const vaultPseudoAcct = Account("VaultPseudo", vaultPseudo); env(trust(issuer, vaultPseudoAcct["IOU"](0), tfSetFreeze)); - env(loanBroker::set(lender, vaultKeylet.key), Ter(tecFROZEN)); + env(loan_broker::set(lender, vaultKeylet.key), Ter(tecFROZEN)); } void @@ -1629,7 +1629,7 @@ class LoanBroker_test : public beast::unit_test::Suite { testcase << "LoanBrokerDelete - locked broker pseudo-account MPT"; using namespace jtx; - using namespace loanBroker; + using namespace loan_broker; Account const issuer("issuer"); Account const alice("alice"); @@ -1749,7 +1749,7 @@ class LoanBroker_test : public beast::unit_test::Suite { testcase << "LoanBrokerDelete - frozen broker pseudo-account IOU"; using namespace jtx; - using namespace loanBroker; + using namespace loan_broker; Account const issuer("issuer"); Account const alice("alice"); @@ -1833,7 +1833,7 @@ class LoanBroker_test : public beast::unit_test::Suite testCoverDepositFreezes() { using namespace jtx; - using namespace loanBroker; + using namespace loan_broker; Account const issuer{"issuer"}; Account const alice{"alice"}; @@ -1984,7 +1984,7 @@ class LoanBroker_test : public beast::unit_test::Suite testcase("LoanBrokerCoverWithdraw IOU self-withdrawal while individually frozen"); using namespace jtx; - using namespace loanBroker; + using namespace loan_broker; Account const issuer{"issuer"}; Account const alice{"alice"}; @@ -2046,7 +2046,7 @@ class LoanBroker_test : public beast::unit_test::Suite testCoverWithdrawFreezes() { using namespace jtx; - using namespace loanBroker; + using namespace loan_broker; Account const issuer{"issuer"}; Account const alice{"alice"}; @@ -2351,7 +2351,7 @@ class LoanBroker_test : public beast::unit_test::Suite env.close(); env(vault.withdraw({.depositor = broker, .id = keylet.key, .amount = token(1'000)}), - loanBroker::kDestination(dest), + loan_broker::kDestination(dest), Ter(std::ignore)); BEAST_EXPECT(env.ter() == tecNO_LINE); env.close(); @@ -2361,14 +2361,14 @@ class LoanBroker_test : public beast::unit_test::Suite // Test LoanBroker withdraw auto const brokerKeylet = keylet::loanBroker(broker, env.seq(broker)); - env(loanBroker::set(broker, keylet.key)); + env(loan_broker::set(broker, keylet.key)); env.close(); - env(loanBroker::coverDeposit(broker, brokerKeylet.key, token(1'000))); + env(loan_broker::coverDeposit(broker, brokerKeylet.key, token(1'000))); env.close(); - env(loanBroker::coverWithdraw(broker, brokerKeylet.key, token(100)), - loanBroker::kDestination(dest), + env(loan_broker::coverWithdraw(broker, brokerKeylet.key, token(100)), + loan_broker::kDestination(dest), Ter(std::ignore)); BEAST_EXPECT(env.ter() == tecNO_LINE); env.close(); @@ -2379,8 +2379,8 @@ class LoanBroker_test : public beast::unit_test::Suite env(fclear(issuer, asfRequireAuth)); env.close(); - env(loanBroker::coverWithdraw(broker, brokerKeylet.key, token(100)), - loanBroker::kDestination(dest), + env(loan_broker::coverWithdraw(broker, brokerKeylet.key, token(100)), + loan_broker::kDestination(dest), Ter(std::ignore)); BEAST_EXPECT(env.ter() == tecNO_LINE); env.close(); @@ -2472,7 +2472,7 @@ class LoanBroker_test : public beast::unit_test::Suite env.close(); env(vault.withdraw({.depositor = broker, .id = keylet.key, .amount = token(1'000)}), - loanBroker::kDestination(dest), + loan_broker::kDestination(dest), Ter(std::ignore)); // Shouldn't fail if at MaximumAmount since no new tokens are issued @@ -2489,14 +2489,14 @@ class LoanBroker_test : public beast::unit_test::Suite // Test LoanBroker withdraw auto const brokerKeylet = keylet::loanBroker(broker, env.seq(broker)); - env(loanBroker::set(broker, keylet.key)); + env(loan_broker::set(broker, keylet.key)); env.close(); - env(loanBroker::coverDeposit(broker, brokerKeylet.key, token(1'000))); + env(loan_broker::coverDeposit(broker, brokerKeylet.key, token(1'000))); env.close(); - env(loanBroker::coverWithdraw(broker, brokerKeylet.key, token(100)), - loanBroker::kDestination(dest), + env(loan_broker::coverWithdraw(broker, brokerKeylet.key, token(100)), + loan_broker::kDestination(dest), Ter(std::ignore)); BEAST_EXPECT(env.ter() == err); env.close(); @@ -2522,7 +2522,7 @@ class LoanBroker_test : public beast::unit_test::Suite testCoverPrecisionGuard() { using namespace jtx; - using namespace loanBroker; + using namespace loan_broker; Account const issuer{"issuer"}; Account const alice{"alice"}; diff --git a/src/test/app/Loan_test.cpp b/src/test/app/Loan_test.cpp index 8a6f1669df..977cdb443c 100644 --- a/src/test/app/Loan_test.cpp +++ b/src/test/app/Loan_test.cpp @@ -545,7 +545,7 @@ protected: auto const keylet = keylet::loanBroker(lender.id(), env.seq(lender)); - using namespace loanBroker; + using namespace loan_broker; env(set(lender, vaultKeylet.key, params.flags), kData(params.data), kManagementFeeRate(params.managementFeeRate), @@ -1533,7 +1533,7 @@ protected: auto const borrowerStartingBalance = env.balance(borrower, broker.asset); // Try to delete the loan broker with an active loan - env(loanBroker::del(lender, broker.brokerID), Ter(tecHAS_OBLIGATIONS)); + env(loan_broker::del(lender, broker.brokerID), Ter(tecHAS_OBLIGATIONS)); // Ensure the above tx doesn't get ordered after the LoanDelete and // delete our broker! env.close(); @@ -1608,7 +1608,7 @@ protected: int interestExponent) { using namespace jtx; - using namespace Lending; + using namespace lending; auto const& asset = broker.asset.raw(); auto const currencyLabel = getCurrencyLabel(asset); @@ -2072,7 +2072,7 @@ protected: Number const& startingCoverAvailable, Number const& amountToBeCovered) { coverAvailable(broker.brokerID, startingCoverAvailable - amountToBeCovered); - env(loanBroker::coverDeposit( + env(loan_broker::coverDeposit( brokerAcct, broker.brokerID, STAmount{broker.asset, amountToBeCovered})); coverAvailable(broker.brokerID, startingCoverAvailable); env.close(); @@ -3572,7 +3572,7 @@ protected: BEAST_EXPECT(brokerSle->at(sfDebtTotal) == 0); auto const coverAvailable = brokerSle->at(sfCoverAvailable); - env(loanBroker::coverWithdraw( + env(loan_broker::coverWithdraw( lender, broker.brokerID, STAmount(broker.asset, coverAvailable))); env.close(); @@ -3580,7 +3580,7 @@ protected: BEAST_EXPECT(brokerSle && brokerSle->at(sfCoverAvailable) == 0); } // Verify we can delete the loan broker - env(loanBroker::del(lender, broker.brokerID)); + env(loan_broker::del(lender, broker.brokerID)); env.close(); } } @@ -4659,7 +4659,7 @@ protected: using namespace jtx; using namespace std::chrono_literals; - using namespace Lending; + using namespace lending; Env env(*this, features); Account const issuer{"issuer"}; @@ -4818,7 +4818,7 @@ protected: using namespace jtx; using namespace std::chrono_literals; - using namespace Lending; + using namespace lending; Env env(*this, features); Account const issuer{"issuer"}; @@ -4959,7 +4959,7 @@ protected: using namespace jtx; using namespace std::chrono_literals; - using namespace Lending; + using namespace lending; Env env(*this, features); Account const issuer{"issuer"}; @@ -5060,7 +5060,7 @@ protected: using namespace jtx; using namespace std::chrono_literals; - using namespace Lending; + using namespace lending; Env env(*this, features); Account const issuer{"issuer"}; @@ -5084,7 +5084,7 @@ protected: BrokerInfo const broker{createVaultAndBroker(env, iouAsset, lender)}; { auto const coverDepositValue = broker.asset(broker.params.coverDeposit * 10).value(); - env(loanBroker::coverDeposit(lender, broker.brokerID, coverDepositValue)); + env(loan_broker::coverDeposit(lender, broker.brokerID, coverDepositValue)); env.close(); } @@ -5145,7 +5145,7 @@ protected: using namespace jtx; using namespace std::chrono_literals; - using namespace Lending; + using namespace lending; Env env{*this, features}; Account const issuer{"issuer"}; @@ -5450,7 +5450,7 @@ protected: testcase("Lending: CanTrade disabled has no impact"); using namespace jtx; using namespace loan; - using namespace loanBroker; + using namespace loan_broker; Env env(*this, all_); @@ -5671,7 +5671,7 @@ protected: using namespace jtx; using namespace loan; - using namespace loanBroker; + using namespace loan_broker; Env env(*this, features); @@ -6283,7 +6283,7 @@ protected: auto const brokerKeyLet = keylet::loanBroker(lender.id(), env.seq(lender)); - env(loanBroker::set(lender, vaultKeyLet.key), txFee); + env(loan_broker::set(lender, vaultKeyLet.key), txFee); env.close(); // BrokerInfo brokerInfo{xrpIssue(), keylet, vaultKeyLet, {}}; @@ -6317,7 +6317,7 @@ protected: testcase("Minimum cover rounding allows undercoverage (XRP)"); using namespace jtx; - using namespace loanBroker; + using namespace loan_broker; Env env{*this, features}; @@ -6488,7 +6488,7 @@ protected: auto const brokerKeylet = keylet::loanBroker(broker.id(), env.seq(broker)); - env(loanBroker::set(broker, vaultKeylet.key), txFee); + env(loan_broker::set(broker, vaultKeylet.key), txFee); env.close(); auto const serviceFee = 101; @@ -6767,7 +6767,7 @@ protected: // at least 1,000 cover. Default cover is 1,000, so we add more to be // safe. auto const additionalCover = iou(50'000).value(); - env(loanBroker::coverDeposit(broker, brokerInfo.brokerID, STAmount{iou, additionalCover})); + env(loan_broker::coverDeposit(broker, brokerInfo.brokerID, STAmount{iou, additionalCover})); env.close(); // Verify broker owner has a trustline auto const brokerTrustline = keylet::trustLine(broker, iou); @@ -6847,7 +6847,7 @@ protected: // at least 1,000 cover. Default cover is 1,000, so we add more to be // safe. auto const additionalCover = mpt(50'000).value(); - env(loanBroker::coverDeposit(broker, brokerInfo.brokerID, STAmount{mpt, additionalCover})); + env(loan_broker::coverDeposit(broker, brokerInfo.brokerID, STAmount{mpt, additionalCover})); env.close(); // Verify broker owner is authorized auto const brokerMpt = keylet::mptoken(mptt.issuanceID(), broker); @@ -6947,7 +6947,7 @@ protected: // at least 1,000 cover. Default cover is 1,000, so we add more to be // safe. auto const additionalCover = mpt(50'000).value(); - env(loanBroker::coverDeposit(broker, brokerInfo.brokerID, STAmount{mpt, additionalCover})); + env(loan_broker::coverDeposit(broker, brokerInfo.brokerID, STAmount{mpt, additionalCover})); env.close(); // Verify broker owner is authorized auto const brokerMpt = keylet::mptoken(mptt.issuanceID(), broker); @@ -7062,7 +7062,7 @@ protected: using namespace jtx; using namespace loan; - using namespace loanBroker; + using namespace loan_broker; Env env{*this, features}; @@ -7923,8 +7923,8 @@ protected: env.close(); auto const brokerKeylet = keylet::loanBroker(lender.id(), env.seq(lender)); - env(loanBroker::set(lender, vaultKeylet.key), - loanBroker::kDebtMaximum(Number{100}), + env(loan_broker::set(lender, vaultKeylet.key), + loan_broker::kDebtMaximum(Number{100}), Fee(env.current()->fees().base * 2)); env.close(); @@ -8168,7 +8168,7 @@ protected: { using namespace jtx; using namespace loan; - using namespace loanBroker; + using namespace loan_broker; bool const withAmendment = features[fixCleanup3_2_0]; @@ -8726,9 +8726,9 @@ protected: BrokerInfo const broker{createVaultAndBroker(env, xrpAsset, lender, brokerParams)}; - env(loanBroker::set(lender, broker.vaultID), - loanBroker::kLoanBrokerId(broker.brokerID), - loanBroker::kDebtMaximum(debtMaximum), + env(loan_broker::set(lender, broker.vaultID), + loan_broker::kLoanBrokerId(broker.brokerID), + loan_broker::kDebtMaximum(debtMaximum), Fee(env.current()->fees().base * 2)); env.close(); diff --git a/src/test/app/PathMPT_test.cpp b/src/test/app/PathMPT_test.cpp index 3ba67b58a6..ff4a024cb8 100644 --- a/src/test/app/PathMPT_test.cpp +++ b/src/test/app/PathMPT_test.cpp @@ -112,10 +112,10 @@ public: MPTTester({.env = env, .issuer = gw, .holders = {alice, bob}, .maxAmt = 100}); auto& app = env.app(); - Resource::Charge loadType = Resource::kFeeReferenceRpc; - Resource::Consumer c; + resource::Charge loadType = resource::kFeeReferenceRpc; + resource::Consumer c; - RPC::JsonContext context{ + rpc::JsonContext context{ {.j = env.journal, .app = app, .loadType = loadType, @@ -125,39 +125,39 @@ public: .role = Role::USER, .coro = {}, .infoSub = {}, - .apiVersion = RPC::kApiVersionIfUnspecified}, + .apiVersion = rpc::kApiVersionIfUnspecified}, {}, {}}; json::Value result; Gate g; - // Test RPC::Tuning::max_src_cur source currencies. + // Test rpc::tuning::max_src_cur source currencies. std::vector numSrc; - numSrc.reserve(RPC::Tuning::kMaxSrcCur); - for (std::uint8_t i = 0; i < RPC::Tuning::kMaxSrcCur; ++i) + numSrc.reserve(rpc::tuning::kMaxSrcCur); + for (std::uint8_t i = 0; i < rpc::tuning::kMaxSrcCur; ++i) numSrc.push_back(makeMptID(i, bob)); app.getJobQueue().postCoro(JtClient, "RPC-Client", [&](auto const& coro) { context.params = xrpl::test::detail::rpf(alice, bob, usd, numSrc); context.coro = coro; - RPC::doCommand(context, result); + rpc::doCommand(context, result); g.signal(); }); BEAST_EXPECT(g.waitFor(5s)); BEAST_EXPECT(!result.isMember(jss::error)); - // Test more than RPC::Tuning::max_src_cur source currencies. - numSrc.push_back(makeMptID(RPC::Tuning::kMaxSrcCur, bob)); + // Test more than rpc::tuning::max_src_cur source currencies. + numSrc.push_back(makeMptID(rpc::tuning::kMaxSrcCur, bob)); app.getJobQueue().postCoro(JtClient, "RPC-Client", [&](auto const& coro) { context.params = xrpl::test::detail::rpf(alice, bob, usd, numSrc); context.coro = coro; - RPC::doCommand(context, result); + rpc::doCommand(context, result); g.signal(); }); BEAST_EXPECT(g.waitFor(5s)); BEAST_EXPECT(result.isMember(jss::error)); - // Test RPC::Tuning::max_auto_src_cur source currencies. + // Test rpc::tuning::max_auto_src_cur source currencies. numSrc.clear(); - for (auto i = 0; i < (RPC::Tuning::kMaxAutoSrcCur - 1); ++i) + for (auto i = 0; i < (rpc::tuning::kMaxAutoSrcCur - 1); ++i) { auto curm = MPTTester({.env = env, .issuer = alice, .holders = {bob}}); numSrc.push_back(curm.issuanceID()); @@ -165,18 +165,18 @@ public: app.getJobQueue().postCoro(JtClient, "RPC-Client", [&](auto const& coro) { context.params = xrpl::test::detail::rpf(alice, bob, usd, {}); context.coro = coro; - RPC::doCommand(context, result); + rpc::doCommand(context, result); g.signal(); }); BEAST_EXPECT(g.waitFor(5s)); BEAST_EXPECT(!result.isMember(jss::error)); - // Test more than RPC::Tuning::max_auto_src_cur source currencies. + // Test more than rpc::tuning::max_auto_src_cur source currencies. auto curm = MPTTester({.env = env, .issuer = alice, .holders = {bob}}); app.getJobQueue().postCoro(JtClient, "RPC-Client", [&](auto const& coro) { context.params = xrpl::test::detail::rpf(alice, bob, usd, {}); context.coro = coro; - RPC::doCommand(context, result); + rpc::doCommand(context, result); g.signal(); }); BEAST_EXPECT(g.waitFor(5s)); diff --git a/src/test/app/Path_test.cpp b/src/test/app/Path_test.cpp index 8f19a419a0..409a9e86f8 100644 --- a/src/test/app/Path_test.cpp +++ b/src/test/app/Path_test.cpp @@ -151,10 +151,10 @@ public: using namespace jtx; auto& app = env.app(); - Resource::Charge loadType = Resource::kFeeReferenceRpc; - Resource::Consumer c; + resource::Charge loadType = resource::kFeeReferenceRpc; + resource::Consumer c; - RPC::JsonContext context{ + rpc::JsonContext context{ {.j = env.journal, .app = app, .loadType = loadType, @@ -164,7 +164,7 @@ public: .role = Role::USER, .coro = {}, .infoSub = {}, - .apiVersion = RPC::kApiVersionIfUnspecified}, + .apiVersion = rpc::kApiVersionIfUnspecified}, {}, {}}; @@ -190,7 +190,7 @@ public: app.getJobQueue().postCoro(JtClient, "RPC-Client", [&](auto const& coro) { context.params = std::move(params); context.coro = coro; - RPC::doCommand(context, result); + rpc::doCommand(context, result); g.signal(); }); @@ -262,10 +262,10 @@ public: env.close(); auto& app = env.app(); - Resource::Charge loadType = Resource::kFeeReferenceRpc; - Resource::Consumer c; + resource::Charge loadType = resource::kFeeReferenceRpc; + resource::Consumer c; - RPC::JsonContext context{ + rpc::JsonContext context{ {.j = env.journal, .app = app, .loadType = loadType, @@ -275,49 +275,49 @@ public: .role = Role::USER, .coro = {}, .infoSub = {}, - .apiVersion = RPC::kApiVersionIfUnspecified}, + .apiVersion = rpc::kApiVersionIfUnspecified}, {}, {}}; json::Value result; Gate g; - // Test RPC::Tuning::max_src_cur source currencies. + // Test rpc::tuning::max_src_cur source currencies. app.getJobQueue().postCoro(JtClient, "RPC-Client", [&](auto const& coro) { - context.params = rpf(Account("alice"), Account("bob"), RPC::Tuning::kMaxSrcCur); + context.params = rpf(Account("alice"), Account("bob"), rpc::tuning::kMaxSrcCur); context.coro = coro; - RPC::doCommand(context, result); + rpc::doCommand(context, result); g.signal(); }); BEAST_EXPECT(g.waitFor(5s)); BEAST_EXPECT(!result.isMember(jss::error)); - // Test more than RPC::Tuning::max_src_cur source currencies. + // Test more than rpc::tuning::max_src_cur source currencies. app.getJobQueue().postCoro(JtClient, "RPC-Client", [&](auto const& coro) { - context.params = rpf(Account("alice"), Account("bob"), RPC::Tuning::kMaxSrcCur + 1); + context.params = rpf(Account("alice"), Account("bob"), rpc::tuning::kMaxSrcCur + 1); context.coro = coro; - RPC::doCommand(context, result); + rpc::doCommand(context, result); g.signal(); }); BEAST_EXPECT(g.waitFor(5s)); BEAST_EXPECT(result.isMember(jss::error)); - // Test RPC::Tuning::max_auto_src_cur source currencies. - for (auto i = 0; i < (RPC::Tuning::kMaxAutoSrcCur - 1); ++i) + // Test rpc::tuning::max_auto_src_cur source currencies. + for (auto i = 0; i < (rpc::tuning::kMaxAutoSrcCur - 1); ++i) env.trust(Account("alice")[std::to_string(i + 100)](100), "bob"); app.getJobQueue().postCoro(JtClient, "RPC-Client", [&](auto const& coro) { context.params = rpf(Account("alice"), Account("bob"), 0); context.coro = coro; - RPC::doCommand(context, result); + rpc::doCommand(context, result); g.signal(); }); BEAST_EXPECT(g.waitFor(5s)); BEAST_EXPECT(!result.isMember(jss::error)); - // Test more than RPC::Tuning::max_auto_src_cur source currencies. + // Test more than rpc::tuning::max_auto_src_cur source currencies. env.trust(Account("alice")["AUD"](100), "bob"); app.getJobQueue().postCoro(JtClient, "RPC-Client", [&](auto const& coro) { context.params = rpf(Account("alice"), Account("bob"), 0); context.coro = coro; - RPC::doCommand(context, result); + rpc::doCommand(context, result); g.signal(); }); BEAST_EXPECT(g.waitFor(5s)); diff --git a/src/test/app/PermissionedDEX_test.cpp b/src/test/app/PermissionedDEX_test.cpp index 67cb7602a0..68b2fa99a7 100644 --- a/src/test/app/PermissionedDEX_test.cpp +++ b/src/test/app/PermissionedDEX_test.cpp @@ -1816,7 +1816,7 @@ class PermissionedDEX_test : public beast::unit_test::Suite env.fund(XRP(1000), carol); env.close(); - env(ledgerStateFix::bookExchangeRate(carol, uint256{1}), Ter(temDISABLED)); + env(ledger_state_fix::bookExchangeRate(carol, uint256{1}), Ter(temDISABLED)); } { @@ -1829,13 +1829,13 @@ class PermissionedDEX_test : public beast::unit_test::Suite env.close(); // BookExchangeRate fixes require sfBookDirectory. - auto missingBookDirectory = ledgerStateFix::bookExchangeRate(carol, uint256{1}); + auto missingBookDirectory = ledger_state_fix::bookExchangeRate(carol, uint256{1}); missingBookDirectory.removeMember(sfBookDirectory.jsonName); env(missingBookDirectory, Ter(temINVALID)); // BookExchangeRate fixes reject fields that belong to other // LedgerStateFix types. - auto extraOwner = ledgerStateFix::bookExchangeRate(carol, uint256{1}); + auto extraOwner = ledger_state_fix::bookExchangeRate(carol, uint256{1}); extraOwner[sfOwner.jsonName] = carol.human(); env(extraOwner, Ter(temINVALID)); } @@ -1847,7 +1847,7 @@ class PermissionedDEX_test : public beast::unit_test::Suite { // Preclaim check: the target directory must exist. - env(ledgerStateFix::bookExchangeRate(setup.carol, uint256{1}), + env(ledger_state_fix::bookExchangeRate(setup.carol, uint256{1}), Fee(fixFee), Ter(tecOBJECT_NOT_FOUND)); } @@ -1861,7 +1861,7 @@ class PermissionedDEX_test : public beast::unit_test::Suite BEAST_EXPECT(ownerDirSle); BEAST_EXPECT(!ownerDirSle->isFieldPresent(sfExchangeRate)); - env(ledgerStateFix::bookExchangeRate(setup.carol, ownerDir.key), + env(ledger_state_fix::bookExchangeRate(setup.carol, ownerDir.key), Fee(fixFee), Ter(tecNO_PERMISSION)); } @@ -1885,7 +1885,7 @@ class PermissionedDEX_test : public beast::unit_test::Suite BEAST_EXPECT(exchangeRate == quality); } - env(ledgerStateFix::bookExchangeRate(setup.carol, dirKey), + env(ledger_state_fix::bookExchangeRate(setup.carol, dirKey), Fee(fixFee), Ter(tecNO_PERMISSION)); } @@ -1932,7 +1932,7 @@ class PermissionedDEX_test : public beast::unit_test::Suite env.close(); auto const fixFee = drops(env.current()->fees().increment); - env(ledgerStateFix::bookExchangeRate(carol_, openDirKey), Fee(fixFee)); + env(ledger_state_fix::bookExchangeRate(carol_, openDirKey), Fee(fixFee)); env.close(); // Confirm sfExchangeRate now matches the key quality. @@ -1947,7 +1947,7 @@ class PermissionedDEX_test : public beast::unit_test::Suite } // Submitting again should fail — nothing to fix. - env(ledgerStateFix::bookExchangeRate(carol_, openDirKey), + env(ledger_state_fix::bookExchangeRate(carol_, openDirKey), Fee(fixFee), Ter(tecNO_PERMISSION)); } diff --git a/src/test/app/SHAMapStore_test.cpp b/src/test/app/SHAMapStore_test.cpp index 537ee4c177..6ee7442d23 100644 --- a/src/test/app/SHAMapStore_test.cpp +++ b/src/test/app/SHAMapStore_test.cpp @@ -60,7 +60,7 @@ class SHAMapStore_test : public beast::unit_test::Suite static bool goodLedger(jtx::Env& env, json::Value const& json, std::string ledgerID, bool checkDB = false) { - auto good = json.isMember(jss::result) && !RPC::containsError(json[jss::result]) && + auto good = json.isMember(jss::result) && !rpc::containsError(json[jss::result]) && json[jss::result][jss::ledger][jss::ledger_index] == ledgerID; if (!good || !checkDB) return good; @@ -99,7 +99,7 @@ class SHAMapStore_test : public beast::unit_test::Suite static bool bad(json::Value const& json, ErrorCodeI error = RpcLgrNotFound) { - return json.isMember(jss::result) && RPC::containsError(json[jss::result]) && + return json.isMember(jss::result) && rpc::containsError(json[jss::result]) && json[jss::result][jss::error_code] == error; } @@ -347,11 +347,11 @@ public: BEAST_EXPECT(lastRotated != 2); auto canDelete = env.rpc("can_delete"); - BEAST_EXPECT(!RPC::containsError(canDelete[jss::result])); + BEAST_EXPECT(!rpc::containsError(canDelete[jss::result])); BEAST_EXPECT(canDelete[jss::result][jss::can_delete] == 0); canDelete = env.rpc("can_delete", "never"); - BEAST_EXPECT(!RPC::containsError(canDelete[jss::result])); + BEAST_EXPECT(!rpc::containsError(canDelete[jss::result])); BEAST_EXPECT(canDelete[jss::result][jss::can_delete] == 0); auto const firstBatch = kDeleteInterval + ledgerSeq; @@ -370,7 +370,7 @@ public: // This does not kick off a cleanup canDelete = env.rpc("can_delete", std::to_string(ledgerSeq + (kDeleteInterval / 2))); - BEAST_EXPECT(!RPC::containsError(canDelete[jss::result])); + BEAST_EXPECT(!rpc::containsError(canDelete[jss::result])); BEAST_EXPECT(canDelete[jss::result][jss::can_delete] == ledgerSeq + (kDeleteInterval / 2)); store.rendezvous(); @@ -423,7 +423,7 @@ public: // This does not kick off a cleanup canDelete = env.rpc("can_delete", "always"); - BEAST_EXPECT(!RPC::containsError(canDelete[jss::result])); + BEAST_EXPECT(!rpc::containsError(canDelete[jss::result])); BEAST_EXPECT( canDelete[jss::result][jss::can_delete] == std::numeric_limits::max()); @@ -457,7 +457,7 @@ public: // This does not kick off a cleanup canDelete = env.rpc("can_delete", "now"); - BEAST_EXPECT(!RPC::containsError(canDelete[jss::result])); + BEAST_EXPECT(!rpc::containsError(canDelete[jss::result])); BEAST_EXPECT(canDelete[jss::result][jss::can_delete] == ledgerSeq - 1); for (; ledgerSeq < lastRotated + kDeleteInterval; ++ledgerSeq) diff --git a/src/test/app/Sponsor_test.cpp b/src/test/app/Sponsor_test.cpp index f20aac68f9..393a6e58f7 100644 --- a/src/test/app/Sponsor_test.cpp +++ b/src/test/app/Sponsor_test.cpp @@ -1664,11 +1664,11 @@ public: env.close(); auto const brokerKeylet = keylet::loanBroker(alice.id(), env.seq(alice)); - env(loanBroker::set(alice, vaultKeylet.key), - loanBroker::kDebtMaximum(xrpAsset(1000).value()), - loanBroker::kManagementFeeRate(TenthBips16{0}), - loanBroker::kCoverRateMinimum(TenthBips32{0}), - loanBroker::kCoverRateLiquidation(TenthBips32{0})); + env(loan_broker::set(alice, vaultKeylet.key), + loan_broker::kDebtMaximum(xrpAsset(1000).value()), + loan_broker::kManagementFeeRate(TenthBips16{0}), + loan_broker::kCoverRateMinimum(TenthBips32{0}), + loan_broker::kCoverRateLiquidation(TenthBips32{0})); env.close(); auto const loanKeylet = keylet::loan(brokerKeylet.key, 1); @@ -2019,7 +2019,7 @@ public: env(sponsor::set_fee(sponsor, 0, fixFee), sponsor::SponseeAcc(alice)); env.close(); - env(ledgerStateFix::nftPageLinks(alice, alice), + env(ledger_state_fix::nftPageLinks(alice, alice), Fee(fixFee), sponsor::As(sponsor, spfSponsorFee), Ter(tecFAILED_PROCESSING)); @@ -2043,7 +2043,7 @@ public: OpenView overlay(&*env.closed()); auto jt = env.jt( - ledgerStateFix::nftPageLinks(alice, alice), + ledger_state_fix::nftPageLinks(alice, alice), Fee(fixFee), sponsor::As(sponsor, spfSponsorFee)); diff --git a/src/test/app/TxQ_test.cpp b/src/test/app/TxQ_test.cpp index 3175e742d9..1fe48add27 100644 --- a/src/test/app/TxQ_test.cpp +++ b/src/test/app/TxQ_test.cpp @@ -2571,7 +2571,7 @@ public: auto fee = env.rpc("fee"); if (BEAST_EXPECT(fee.isMember(jss::result)) && - BEAST_EXPECT(!RPC::containsError(fee[jss::result]))) + BEAST_EXPECT(!rpc::containsError(fee[jss::result]))) { auto const& result = fee[jss::result]; BEAST_EXPECT( @@ -2600,7 +2600,7 @@ public: fee = env.rpc("fee"); if (BEAST_EXPECT(fee.isMember(jss::result)) && - BEAST_EXPECT(!RPC::containsError(fee[jss::result]))) + BEAST_EXPECT(!rpc::containsError(fee[jss::result]))) { auto const& result = fee[jss::result]; BEAST_EXPECT( @@ -3225,7 +3225,7 @@ public: { auto const info = env.rpc("json", "account_info", to_string(prevLedgerWithQueue)); - BEAST_EXPECT(info.isMember(jss::result) && RPC::containsError(info[jss::result])); + BEAST_EXPECT(info.isMember(jss::result) && rpc::containsError(info[jss::result])); } env.close(); @@ -4630,7 +4630,7 @@ public: auto const fee = env.rpc("fee"); if (BEAST_EXPECT(fee.isMember(jss::result)) && - BEAST_EXPECT(!RPC::containsError(fee[jss::result]))) + BEAST_EXPECT(!rpc::containsError(fee[jss::result]))) { auto const& result = fee[jss::result]; @@ -4688,7 +4688,7 @@ public: auto const fee = env.rpc("fee"); if (BEAST_EXPECT(fee.isMember(jss::result)) && - BEAST_EXPECT(!RPC::containsError(fee[jss::result]))) + BEAST_EXPECT(!rpc::containsError(fee[jss::result]))) { auto const& result = fee[jss::result]; diff --git a/src/test/app/Vault_test.cpp b/src/test/app/Vault_test.cpp index bd596d6149..791cf216c3 100644 --- a/src/test/app/Vault_test.cpp +++ b/src/test/app/Vault_test.cpp @@ -3936,7 +3936,7 @@ class Vault_test : public beast::unit_test::Suite // Deposit 100 IOU → 1000 shares. Borrow 40 → assetsAvailable=60. // Clawback 80 IOU → clamped to 60, then share math uses truncation. testCase(1, [&, this](Env& env, Data d) { - using namespace loanBroker; + using namespace loan_broker; using namespace loan; testcase("Scale clawback clamped with outstanding loan"); @@ -4410,7 +4410,7 @@ class Vault_test : public beast::unit_test::Suite testVaultClawbackBurnShares() { using namespace test::jtx; - using namespace loanBroker; + using namespace loan_broker; using namespace loan; Env env(*this, beast::Severity::Warning); @@ -4670,7 +4670,7 @@ class Vault_test : public beast::unit_test::Suite testVaultClawbackAssets() { using namespace test::jtx; - using namespace loanBroker; + using namespace loan_broker; using namespace loan; Env env(*this); env.enableFeature(fixCleanup3_1_3); @@ -6093,7 +6093,7 @@ class Vault_test : public beast::unit_test::Suite // Loan broker: no cover, no management fee, debt cap 10x principal. f.brokerID = keylet::loanBroker(f.lender.id(), env.seq(f.lender)).key; { - using namespace loanBroker; + using namespace loan_broker; env(set(f.lender, vaultKeylet.key), kDebtMaximum((*f.asset)(kStuckPrincipal * 10).value())); env.close(); diff --git a/src/test/basics/PerfLog_test.cpp b/src/test/basics/PerfLog_test.cpp index 41b5f81f5d..24ea971515 100644 --- a/src/test/basics/PerfLog_test.cpp +++ b/src/test/basics/PerfLog_test.cpp @@ -312,7 +312,7 @@ public: // Get the all the labels we can use for RPC interfaces without // causing an assert. - std::vector labels = test::jtx::makeVector(xrpl::RPC::getHandlerNames()); + std::vector labels = test::jtx::makeVector(xrpl::rpc::getHandlerNames()); std::shuffle(labels.begin(), labels.end(), defaultPrng()); // Get two IDs to associate with each label. Errors tend to happen at @@ -483,7 +483,7 @@ public: json::Value parsedLastLine; json::Reader().parse(lastLine, parsedLastLine); - if (!BEAST_EXPECT(!RPC::containsError(parsedLastLine))) + if (!BEAST_EXPECT(!rpc::containsError(parsedLastLine))) { // Avoid cascade of failures return; @@ -804,7 +804,7 @@ public: json::Value parsedLastLine; json::Reader().parse(lastLine, parsedLastLine); - if (!BEAST_EXPECT(!RPC::containsError(parsedLastLine))) + if (!BEAST_EXPECT(!rpc::containsError(parsedLastLine))) { // Avoid cascade of failures return; @@ -944,7 +944,7 @@ public: json::Value parsedLastLine; json::Reader().parse(lastLine, parsedLastLine); - if (!BEAST_EXPECT(!RPC::containsError(parsedLastLine))) + if (!BEAST_EXPECT(!rpc::containsError(parsedLastLine))) { // Avoid cascade of failures return; diff --git a/src/test/beast/IPEndpointCommon.h b/src/test/beast/IPEndpointCommon.h index 45d036476c..6fb2bd9569 100644 --- a/src/test/beast/IPEndpointCommon.h +++ b/src/test/beast/IPEndpointCommon.h @@ -7,7 +7,7 @@ #include -namespace beast::IP { +namespace beast::ip { inline Endpoint randomEP(bool v4 = true) @@ -44,4 +44,4 @@ randomEP(bool v4 = true) randInt(1, UINT16_MAX)}; } -} // namespace beast::IP +} // namespace beast::ip diff --git a/src/test/beast/IPEndpoint_test.cpp b/src/test/beast/IPEndpoint_test.cpp index bc04087891..b61878aa76 100644 --- a/src/test/beast/IPEndpoint_test.cpp +++ b/src/test/beast/IPEndpoint_test.cpp @@ -22,7 +22,7 @@ #include #include -namespace beast::IP { +namespace beast::ip { //------------------------------------------------------------------------------ @@ -475,4 +475,4 @@ public: BEAST_DEFINE_TESTSUITE(IPEndpoint, beast, beast); -} // namespace beast::IP +} // namespace beast::ip diff --git a/src/test/jtx/AMM.h b/src/test/jtx/AMM.h index 68b6d9f745..435e32b7b4 100644 --- a/src/test/jtx/AMM.h +++ b/src/test/jtx/AMM.h @@ -199,7 +199,7 @@ public: std::optional const& asset2 = std::nullopt, std::optional const& ammAccount = std::nullopt, bool ignoreParams = false, - unsigned apiVersion = RPC::kApiInvalidVersion) const; + unsigned apiVersion = rpc::kApiInvalidVersion) const; [[nodiscard]] json::Value ammRpcInfo( diff --git a/src/test/jtx/Env.h b/src/test/jtx/Env.h index a175fd5006..5cb841578a 100644 --- a/src/test/jtx/Env.h +++ b/src/test/jtx/Env.h @@ -1081,7 +1081,7 @@ Env::rpc( Args&&... args) { return doRpc( - RPC::kApiCommandLineVersion, + rpc::kApiCommandLineVersion, std::vector{cmd, std::forward(args)...}, headers); } diff --git a/src/test/jtx/TestHelpers.h b/src/test/jtx/TestHelpers.h index e7a2808f07..d5cd8e66b8 100644 --- a/src/test/jtx/TestHelpers.h +++ b/src/test/jtx/TestHelpers.h @@ -876,7 +876,7 @@ checkMetrics( /* LoanBroker */ /******************************************************************************/ -namespace loanBroker { +namespace loan_broker { json::Value set(AccountID const& account, uint256 const& vaultId, std::uint32_t flags = 0); @@ -917,7 +917,7 @@ auto const kCoverRateLiquidation = auto const kDestination = JTxFieldWrapper(sfDestination); -} // namespace loanBroker +} // namespace loan_broker /* Loan */ /******************************************************************************/ diff --git a/src/test/jtx/impl/AMM.cpp b/src/test/jtx/impl/AMM.cpp index 8effce288e..74232037ce 100644 --- a/src/test/jtx/impl/AMM.cpp +++ b/src/test/jtx/impl/AMM.cpp @@ -234,7 +234,7 @@ AMM::ammRpcInfo( jv[jss::amm_account] = *ammAccount; } auto jr = - (apiVersion == RPC::kApiInvalidVersion + (apiVersion == rpc::kApiInvalidVersion ? env_.rpc("json", "amm_info", to_string(jv)) : env_.rpc(apiVersion, "json", "amm_info", to_string(jv))); if (jr.isObject() && jr.isMember(jss::result) && jr[jss::result].isMember(jss::status)) diff --git a/src/test/jtx/impl/Env.cpp b/src/test/jtx/impl/Env.cpp index 4da2e2b521..35553bdeb2 100644 --- a/src/test/jtx/impl/Env.cpp +++ b/src/test/jtx/impl/Env.cpp @@ -498,9 +498,9 @@ Env::postconditions( !test.expect( parsed.rpcCode == jt.rpcCode->first && parsed.rpcMessage == jt.rpcCode->second, "apply " + locStr + ": Got RPC result "s + - (parsed.rpcCode ? RPC::getErrorInfo(*parsed.rpcCode).token.cStr() : "NO RESULT") + + (parsed.rpcCode ? rpc::getErrorInfo(*parsed.rpcCode).token.cStr() : "NO RESULT") + " (" + parsed.rpcMessage + "); Expected " + - RPC::getErrorInfo(jt.rpcCode->first).token.cStr() + " (" + jt.rpcCode->second + + rpc::getErrorInfo(jt.rpcCode->first).token.cStr() + " (" + jt.rpcCode->second + ")")) || bad; // If we have an rpcCode (just checked), then the rpcException check is diff --git a/src/test/jtx/impl/TestHelpers.cpp b/src/test/jtx/impl/TestHelpers.cpp index 4d3869b4f9..d73eb8adf4 100644 --- a/src/test/jtx/impl/TestHelpers.cpp +++ b/src/test/jtx/impl/TestHelpers.cpp @@ -211,10 +211,10 @@ findPathsRequest( using namespace jtx; auto& app = env.app(); - Resource::Charge loadType = Resource::kFeeReferenceRpc; - Resource::Consumer c; + resource::Charge loadType = resource::kFeeReferenceRpc; + resource::Consumer c; - RPC::JsonContext context{ + rpc::JsonContext context{ {.j = env.journal, .app = app, .loadType = loadType, @@ -224,7 +224,7 @@ findPathsRequest( .role = Role::USER, .coro = {}, .infoSub = {}, - .apiVersion = RPC::kApiVersionIfUnspecified}, + .apiVersion = rpc::kApiVersionIfUnspecified}, {}, {}}; @@ -252,7 +252,7 @@ findPathsRequest( app.getJobQueue().postCoro(JtClient, "RPC-Client", [&](auto const& coro) { context.params = std::move(params); context.coro = coro; - RPC::doCommand(context, result); + rpc::doCommand(context, result); g.signal(); }); @@ -743,7 +743,7 @@ issueHelperMPT(IssuerArgs const& args) /* LoanBroker */ /******************************************************************************/ -namespace loanBroker { +namespace loan_broker { json::Value set(AccountID const& account, uint256 const& vaultId, uint32_t flags) @@ -809,7 +809,7 @@ coverClawback(AccountID const& account, std::uint32_t flags) return jv; } -} // namespace loanBroker +} // namespace loan_broker /* Loan */ /******************************************************************************/ diff --git a/src/test/jtx/impl/attester.cpp b/src/test/jtx/impl/attester.cpp index ac946a1bf3..3799d957e9 100644 --- a/src/test/jtx/impl/attester.cpp +++ b/src/test/jtx/impl/attester.cpp @@ -24,7 +24,7 @@ signClaimAttestation( std::uint64_t claimID, std::optional const& dst) { - auto const toSign = Attestations::AttestationClaim::message( + auto const toSign = attestations::AttestationClaim::message( bridge, sendingAccount, sendingAmount, rewardAccount, wasLockingChainSend, claimID, dst); return sign(pk, sk, makeSlice(toSign)); } @@ -42,7 +42,7 @@ signCreateAccountAttestation( std::uint64_t createCount, AccountID const& dst) { - auto const toSign = Attestations::AttestationCreateAccount::message( + auto const toSign = attestations::AttestationCreateAccount::message( bridge, sendingAccount, sendingAmount, diff --git a/src/test/jtx/impl/ledgerStateFixes.cpp b/src/test/jtx/impl/ledgerStateFixes.cpp index 30c6659124..ae195021b8 100644 --- a/src/test/jtx/impl/ledgerStateFixes.cpp +++ b/src/test/jtx/impl/ledgerStateFixes.cpp @@ -9,7 +9,7 @@ #include -namespace xrpl::test::jtx::ledgerStateFix { +namespace xrpl::test::jtx::ledger_state_fix { // Fix NFTokenPage links on owner's account. acct pays fee. json::Value @@ -35,4 +35,4 @@ bookExchangeRate(jtx::Account const& acct, uint256 const& bookDir) return jv; } -} // namespace xrpl::test::jtx::ledgerStateFix +} // namespace xrpl::test::jtx::ledger_state_fix diff --git a/src/test/jtx/ledgerStateFix.h b/src/test/jtx/ledgerStateFix.h index 2fe5c8accc..4ae22f891e 100644 --- a/src/test/jtx/ledgerStateFix.h +++ b/src/test/jtx/ledgerStateFix.h @@ -8,7 +8,7 @@ /** * LedgerStateFix operations. */ -namespace xrpl::test::jtx::ledgerStateFix { +namespace xrpl::test::jtx::ledger_state_fix { /** * Repair the links in an NFToken directory. @@ -22,4 +22,4 @@ nftPageLinks(jtx::Account const& acct, jtx::Account const& owner); json::Value bookExchangeRate(jtx::Account const& acct, uint256 const& bookDir); -} // namespace xrpl::test::jtx::ledgerStateFix +} // namespace xrpl::test::jtx::ledger_state_fix diff --git a/src/test/jtx/rpc.h b/src/test/jtx/rpc.h index 9bd99c15f8..7fd550563c 100644 --- a/src/test/jtx/rpc.h +++ b/src/test/jtx/rpc.h @@ -48,7 +48,7 @@ public: jt.ter = telENV_RPC_FAILED; if (code_) { - auto const& errorInfo = RPC::getErrorInfo(*code_); + auto const& errorInfo = rpc::getErrorInfo(*code_); // When an RPC request returns an error code ('error_code'), it // always includes an error message ('error_message'), and sometimes // includes an error token ('error'). If it does, the error token is diff --git a/src/test/overlay/TMGetObjectByHash_test.cpp b/src/test/overlay/TMGetObjectByHash_test.cpp index f52220a90c..6c9105164a 100644 --- a/src/test/overlay/TMGetObjectByHash_test.cpp +++ b/src/test/overlay/TMGetObjectByHash_test.cpp @@ -44,7 +44,7 @@ using namespace jtx; * Test for TMGetObjectByHash reply size limiting. * * This verifies the fix that limits TMGetObjectByHash replies to - * Tuning::hardMaxReplyNodes to prevent excessive memory usage and + * tuning::hardMaxReplyNodes to prevent excessive memory usage and * potential DoS attacks from peers requesting large numbers of objects. */ class TMGetObjectByHash_test : public beast::unit_test::Suite @@ -61,11 +61,11 @@ class TMGetObjectByHash_test : public beast::unit_test::Suite public: PeerTest( Application& app, - std::shared_ptr const& slot, + std::shared_ptr const& slot, http_request_type&& request, PublicKey const& publicKey, ProtocolVersion protocol, - Resource::Consumer consumer, + resource::Consumer consumer, std::unique_ptr&& streamPtr, OverlayImpl& overlay) : PeerImp( @@ -133,8 +133,8 @@ class TMGetObjectByHash_test : public beast::unit_test::Suite auto streamPtr = std::make_unique(socket_type(env.app().getIOContext()), *context_); - beast::IP::Endpoint const local(boost::asio::ip::make_address("172.1.1.1"), 51235); - beast::IP::Endpoint const remote(boost::asio::ip::make_address("172.1.1.2"), 51235); + beast::ip::Endpoint const local(boost::asio::ip::make_address("172.1.1.1"), 51235); + beast::ip::Endpoint const remote(boost::asio::ip::make_address("172.1.1.2"), 51235); PublicKey const key(std::get<0>(randomKeyPair(KeyType::Ed25519))); auto consumer = overlay.resourceManager().newInboundEndpoint(remote); @@ -227,7 +227,7 @@ class TMGetObjectByHash_test : public beast::unit_test::Suite void run() override { - int const limit = static_cast(Tuning::kHardMaxReplyNodes); + int const limit = static_cast(tuning::kHardMaxReplyNodes); testReplyLimit(limit + 1, limit); testReplyLimit(limit, limit); testReplyLimit(limit - 1, limit - 1); diff --git a/src/test/overlay/compression_test.cpp b/src/test/overlay/compression_test.cpp index 60cc69a14f..a583a3aeab 100644 --- a/src/test/overlay/compression_test.cpp +++ b/src/test/overlay/compression_test.cpp @@ -413,7 +413,7 @@ public: return env; }; auto handshake = [&](int outboundEnable, int inboundEnable) { - beast::IP::Address const addr = boost::asio::ip::make_address("172.1.1.100"); + beast::ip::Address const addr = boost::asio::ip::make_address("172.1.1.100"); auto env = getEnv(outboundEnable); auto request = xrpl::makeRequest( diff --git a/src/test/overlay/reduce_relay_test.cpp b/src/test/overlay/reduce_relay_test.cpp index 2f42313037..77920007de 100644 --- a/src/test/overlay/reduce_relay_test.cpp +++ b/src/test/overlay/reduce_relay_test.cpp @@ -92,13 +92,13 @@ public: send(std::shared_ptr const& m) override { } - [[nodiscard]] beast::IP::Endpoint + [[nodiscard]] beast::ip::Endpoint getRemoteAddress() const override { return {}; } void - charge(Resource::Charge const& fee, std::string const& context = {}) override + charge(resource::Charge const& fee, std::string const& context = {}) override { } [[nodiscard]] bool @@ -1610,7 +1610,7 @@ vp_base_squelch_max_selected_peers=2 env_.app().config().compression = c.compression; }; auto handshake = [&](int outboundEnable, int inboundEnable) { - beast::IP::Address const addr = boost::asio::ip::make_address("172.1.1.100"); + beast::ip::Address const addr = boost::asio::ip::make_address("172.1.1.100"); setEnv(outboundEnable); auto request = xrpl::makeRequest( diff --git a/src/test/overlay/tx_reduce_relay_test.cpp b/src/test/overlay/tx_reduce_relay_test.cpp index 43f6ef2506..8626d3e19c 100644 --- a/src/test/overlay/tx_reduce_relay_test.cpp +++ b/src/test/overlay/tx_reduce_relay_test.cpp @@ -121,11 +121,11 @@ private: public: PeerTest( Application& app, - std::shared_ptr const& slot, + std::shared_ptr const& slot, http_request_type&& request, PublicKey const& publicKey, ProtocolVersion protocol, - Resource::Consumer consumer, + resource::Consumer consumer, std::unique_ptr&& streamPtr, OverlayImpl& overlay) : PeerImp( @@ -192,9 +192,9 @@ private: auto streamPtr = std::make_unique( socket_type(std::forward(env.app().getIOContext())), *context_); - beast::IP::Endpoint const local( + beast::ip::Endpoint const local( boost::asio::ip::make_address("172.1.1." + std::to_string(lid_))); - beast::IP::Endpoint const remote( + beast::ip::Endpoint const remote( boost::asio::ip::make_address("172.1.1." + std::to_string(rid_))); PublicKey const key(std::get<0>(randomKeyPair(KeyType::Ed25519))); auto consumer = overlay.resourceManager().newInboundEndpoint(remote); diff --git a/src/test/protocol/BuildInfo_test.cpp b/src/test/protocol/BuildInfo_test.cpp index 1741f45938..a669e3e292 100644 --- a/src/test/protocol/BuildInfo_test.cpp +++ b/src/test/protocol/BuildInfo_test.cpp @@ -11,7 +11,7 @@ public: { testcase("EncodeSoftwareVersion"); - auto encodedVersion = BuildInfo::encodeSoftwareVersion("1.2.3-b7"); + auto encodedVersion = build_info::encodeSoftwareVersion("1.2.3-b7"); // the first two bytes identify the particular implementation, 0x183B BEAST_EXPECT((encodedVersion & 0xFFFF'0000'0000'0000LLU) == 0x183B'0000'0000'0000LLU); @@ -25,15 +25,15 @@ public: // 01 if a beta BEAST_EXPECT((encodedVersion & 0x0000'0000'00C0'0000LLU) >> 22 == 0b01); // 10 if an RC - encodedVersion = BuildInfo::encodeSoftwareVersion("1.2.4-rc7"); + encodedVersion = build_info::encodeSoftwareVersion("1.2.4-rc7"); BEAST_EXPECT((encodedVersion & 0x0000'0000'00C0'0000LLU) >> 22 == 0b10); // 11 if neither an RC nor a beta - encodedVersion = BuildInfo::encodeSoftwareVersion("1.2.5"); + encodedVersion = build_info::encodeSoftwareVersion("1.2.5"); BEAST_EXPECT((encodedVersion & 0x0000'0000'00C0'0000LLU) >> 22 == 0b11); } // the next six bits: rc/beta number (1-63) - encodedVersion = BuildInfo::encodeSoftwareVersion("1.2.6-b63"); + encodedVersion = build_info::encodeSoftwareVersion("1.2.6-b63"); BEAST_EXPECT((encodedVersion & 0x0000'0000'003F'0000LLU) >> 16 == 63); // the last two bytes are zeros @@ -41,14 +41,14 @@ public: // Test some version strings with wrong formats: // no rc/beta number - encodedVersion = BuildInfo::encodeSoftwareVersion("1.2.3-b"); + encodedVersion = build_info::encodeSoftwareVersion("1.2.3-b"); BEAST_EXPECT((encodedVersion & 0x0000'0000'00FF'0000LLU) == 0); // rc/beta number out of range - encodedVersion = BuildInfo::encodeSoftwareVersion("1.2.3-b64"); + encodedVersion = build_info::encodeSoftwareVersion("1.2.3-b64"); BEAST_EXPECT((encodedVersion & 0x0000'0000'00FF'0000LLU) == 0); // Check that the rc/beta number of a release is 0: - encodedVersion = BuildInfo::encodeSoftwareVersion("1.2.6"); + encodedVersion = build_info::encodeSoftwareVersion("1.2.6"); BEAST_EXPECT((encodedVersion & 0x0000'0000'003F'0000LLU) == 0); } @@ -57,9 +57,9 @@ public: { testcase("IsXrpldVersion"); auto vFF = 0xFFFF'FFFF'FFFF'FFFFLLU; - BEAST_EXPECT(!BuildInfo::isXrpldVersion(vFF)); + BEAST_EXPECT(!build_info::isXrpldVersion(vFF)); auto vXrpld = 0x183B'0000'0000'0000LLU; - BEAST_EXPECT(BuildInfo::isXrpldVersion(vXrpld)); + BEAST_EXPECT(build_info::isXrpldVersion(vXrpld)); } void @@ -67,16 +67,16 @@ public: { testcase("IsNewerVersion"); auto vFF = 0xFFFF'FFFF'FFFF'FFFFLLU; - BEAST_EXPECT(!BuildInfo::isNewerVersion(vFF)); + BEAST_EXPECT(!build_info::isNewerVersion(vFF)); - auto v159 = BuildInfo::encodeSoftwareVersion("1.5.9"); - BEAST_EXPECT(!BuildInfo::isNewerVersion(v159)); + auto v159 = build_info::encodeSoftwareVersion("1.5.9"); + BEAST_EXPECT(!build_info::isNewerVersion(v159)); - auto vCurrent = BuildInfo::getEncodedVersion(); - BEAST_EXPECT(!BuildInfo::isNewerVersion(vCurrent)); + auto vCurrent = build_info::getEncodedVersion(); + BEAST_EXPECT(!build_info::isNewerVersion(vCurrent)); - auto vMax = BuildInfo::encodeSoftwareVersion("255.255.255"); - BEAST_EXPECT(BuildInfo::isNewerVersion(vMax)); + auto vMax = build_info::encodeSoftwareVersion("255.255.255"); + BEAST_EXPECT(build_info::isNewerVersion(vMax)); } void diff --git a/src/test/protocol/InnerObjectFormats_test.cpp b/src/test/protocol/InnerObjectFormats_test.cpp index 5154153ecf..73a283da39 100644 --- a/src/test/protocol/InnerObjectFormats_test.cpp +++ b/src/test/protocol/InnerObjectFormats_test.cpp @@ -5,7 +5,7 @@ #include #include // json::Reader #include -#include // RPC::containsError +#include // rpc::containsError #include // STParsedJSONObject #include @@ -13,7 +13,7 @@ namespace xrpl { -namespace InnerObjectFormatsUnitTestDetail { +namespace inner_object_formats_unit_test_detail { struct TestJSONTxt { @@ -149,7 +149,7 @@ static TestJSONTxt const kTestArray[] = { }; -} // namespace InnerObjectFormatsUnitTestDetail +} // namespace inner_object_formats_unit_test_detail class InnerObjectFormatsParsedJSON_test : public beast::unit_test::Suite { @@ -157,7 +157,7 @@ public: void run() override { - using namespace InnerObjectFormatsUnitTestDetail; + using namespace inner_object_formats_unit_test_detail; // Instantiate a jtx::Env so debugLog writes are exercised. test::jtx::Env const env(*this); @@ -166,7 +166,7 @@ public: { json::Value req; json::Reader().parse(test.txt, req); - if (RPC::containsError(req)) + if (rpc::containsError(req)) { Throw( "Internal InnerObjectFormatsParsedJSON error. Bad JSON."); diff --git a/src/test/protocol/MultiApiJson_test.cpp b/src/test/protocol/MultiApiJson_test.cpp index c6f844a206..2f0d4cb3ec 100644 --- a/src/test/protocol/MultiApiJson_test.cpp +++ b/src/test/protocol/MultiApiJson_test.cpp @@ -62,35 +62,35 @@ struct MultiApiJson_test : beast::unit_test::Suite // Some static data for test inputs static int const kPrimes[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97}; - static_assert(std::size(kPrimes) > RPC::kApiMaximumValidVersion); + static_assert(std::size(kPrimes) > rpc::kApiMaximumValidVersion); MultiApiJson<1, 3> s1{}; static_assert( - s1.kSize == RPC::kApiMaximumValidVersion + 1 - RPC::kApiMinimumSupportedVersion); + s1.kSize == rpc::kApiMaximumValidVersion + 1 - rpc::kApiMinimumSupportedVersion); int productAllVersions = 1; - for (unsigned i = RPC::kApiMinimumSupportedVersion; i <= RPC::kApiMaximumValidVersion; + for (unsigned i = rpc::kApiMinimumSupportedVersion; i <= rpc::kApiMaximumValidVersion; ++i) { - auto const index = i - RPC::kApiMinimumSupportedVersion; + auto const index = i - rpc::kApiMinimumSupportedVersion; BEAST_EXPECT(index == s1.index(i)); BEAST_EXPECT(s1.valid(i)); s1.val[index] = makeJson("value", kPrimes[i]); productAllVersions *= kPrimes[i]; } BEAST_EXPECT(!s1.valid(0)); - BEAST_EXPECT(!s1.valid(RPC::kApiMaximumValidVersion + 1)); + BEAST_EXPECT(!s1.valid(rpc::kApiMaximumValidVersion + 1)); BEAST_EXPECT(!s1.valid( - std::numeric_limits::max())); + std::numeric_limits::max())); int result = 1; - static_assert(RPC::kApiMinimumSupportedVersion + 1 <= RPC::kApiMaximumValidVersion); - forApiVersions( + static_assert(rpc::kApiMinimumSupportedVersion + 1 <= rpc::kApiMaximumValidVersion); + forApiVersions( std::as_const(s1).visit(), [this](json::Value const& json, unsigned int version, int* result) { BEAST_EXPECT( - version >= RPC::kApiMinimumSupportedVersion && - version <= RPC::kApiMinimumSupportedVersion + 1); + version >= rpc::kApiMinimumSupportedVersion && + version <= rpc::kApiMinimumSupportedVersion + 1); if (BEAST_EXPECT(json.isMember("value"))) { *result *= json["value"].asInt(); @@ -99,8 +99,8 @@ struct MultiApiJson_test : beast::unit_test::Suite &result); BEAST_EXPECT( result == - kPrimes[RPC::kApiMinimumSupportedVersion] * - kPrimes[RPC::kApiMinimumSupportedVersion + 1]); + kPrimes[rpc::kApiMinimumSupportedVersion] * + kPrimes[rpc::kApiMinimumSupportedVersion + 1]); // Check all the values with mutable data forAllApiVersions(s1.visit(), [&s1, this](json::Value& json, auto version) { @@ -116,8 +116,8 @@ struct MultiApiJson_test : beast::unit_test::Suite std::as_const(s1).visit(), [this](json::Value const& json, unsigned int version, int* result) { BEAST_EXPECT( - version >= RPC::kApiMinimumSupportedVersion && - version <= RPC::kApiMaximumValidVersion); + version >= rpc::kApiMinimumSupportedVersion && + version <= rpc::kApiMaximumValidVersion); if (BEAST_EXPECT(json.isMember("value"))) { *result *= json["value"].asInt(); diff --git a/src/test/rpc/AccountLines_test.cpp b/src/test/rpc/AccountLines_test.cpp index 8d55c5e19d..cb20de9bf5 100644 --- a/src/test/rpc/AccountLines_test.cpp +++ b/src/test/rpc/AccountLines_test.cpp @@ -34,7 +34,7 @@ #include #include -namespace xrpl::RPC { +namespace xrpl::rpc { class AccountLines_test : public beast::unit_test::Suite { @@ -51,7 +51,7 @@ public: auto const lines = env.rpc("json", "account_lines", "{ }"); BEAST_EXPECT( lines[jss::result][jss::error_message] == - RPC::missingFieldError(jss::account)[jss::error_message]); + rpc::missingFieldError(jss::account)[jss::error_message]); } { // account_lines with a malformed account. @@ -60,7 +60,7 @@ public: auto const lines = env.rpc("json", "account_lines", to_string(params)); BEAST_EXPECT( lines[jss::result][jss::error_message] == - RPC::makeError(RpcActMalformed)[jss::error_message]); + rpc::makeError(RpcActMalformed)[jss::error_message]); } { // test account non-string @@ -87,7 +87,7 @@ public: auto const lines = env.rpc("json", "account_lines", to_string(params)); BEAST_EXPECT( lines[jss::result][jss::error_message] == - RPC::makeError(RpcActNotFound)[jss::error_message]); + rpc::makeError(RpcActNotFound)[jss::error_message]); } env.fund(XRP(10000), alice); env.close(); @@ -250,7 +250,7 @@ public: auto const lines = env.rpc("json", "account_lines", to_string(params)); BEAST_EXPECT( lines[jss::result][jss::error_message] == - RPC::makeError(RpcActMalformed)[jss::error_message]); + rpc::makeError(RpcActMalformed)[jss::error_message]); } { // A negative limit should fail. @@ -260,7 +260,7 @@ public: auto const lines = env.rpc("json", "account_lines", to_string(params)); BEAST_EXPECT( lines[jss::result][jss::error_message] == - RPC::expectedFieldMessage(jss::limit, "unsigned integer")); + rpc::expectedFieldMessage(jss::limit, "unsigned integer")); } { // Limit the response to 1 trust line. @@ -297,7 +297,7 @@ public: auto const linesD = env.rpc("json", "account_lines", to_string(paramsD)); BEAST_EXPECT( linesD[jss::result][jss::error_message] == - RPC::makeError(RpcInvalidParams)[jss::error_message]); + rpc::makeError(RpcInvalidParams)[jss::error_message]); } { // A non-string marker should also fail. @@ -307,7 +307,7 @@ public: auto const lines = env.rpc("json", "account_lines", to_string(params)); BEAST_EXPECT( lines[jss::result][jss::error_message] == - RPC::expectedFieldMessage(jss::marker, "string")); + rpc::expectedFieldMessage(jss::marker, "string")); } { // Check that the flags we expect from alice to gw2 are present. @@ -496,7 +496,7 @@ public: auto const linesEnd = env.rpc("json", "account_lines", to_string(linesEndParams)); BEAST_EXPECT( linesEnd[jss::result][jss::error_message] == - RPC::makeError(RpcInvalidParams)[jss::error_message]); + rpc::makeError(RpcInvalidParams)[jss::error_message]); } void @@ -728,7 +728,7 @@ public: auto const lines = env.rpc("json2", to_string(request)); BEAST_EXPECT( lines[jss::error][jss::message] == - RPC::missingFieldError(jss::account)[jss::error_message]); + rpc::missingFieldError(jss::account)[jss::error_message]); BEAST_EXPECT(lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0"); BEAST_EXPECT(lines.isMember(jss::ripplerpc) && lines[jss::ripplerpc] == "2.0"); BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5); @@ -746,7 +746,7 @@ public: auto const lines = env.rpc("json2", to_string(request)); BEAST_EXPECT( lines[jss::error][jss::message] == - RPC::makeError(RpcActMalformed)[jss::error_message]); + rpc::makeError(RpcActMalformed)[jss::error_message]); BEAST_EXPECT(lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0"); BEAST_EXPECT(lines.isMember(jss::ripplerpc) && lines[jss::ripplerpc] == "2.0"); BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5); @@ -765,7 +765,7 @@ public: auto const lines = env.rpc("json2", to_string(request)); BEAST_EXPECT( lines[jss::error][jss::message] == - RPC::makeError(RpcActNotFound)[jss::error_message]); + rpc::makeError(RpcActNotFound)[jss::error_message]); BEAST_EXPECT(lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0"); BEAST_EXPECT(lines.isMember(jss::ripplerpc) && lines[jss::ripplerpc] == "2.0"); BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5); @@ -998,7 +998,7 @@ public: auto const lines = env.rpc("json2", to_string(request)); BEAST_EXPECT( lines[jss::error][jss::message] == - RPC::makeError(RpcActMalformed)[jss::error_message]); + rpc::makeError(RpcActMalformed)[jss::error_message]); BEAST_EXPECT(lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0"); BEAST_EXPECT(lines.isMember(jss::ripplerpc) && lines[jss::ripplerpc] == "2.0"); BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5); @@ -1017,7 +1017,7 @@ public: auto const lines = env.rpc("json2", to_string(request)); BEAST_EXPECT( lines[jss::error][jss::message] == - RPC::expectedFieldMessage(jss::limit, "unsigned integer")); + rpc::expectedFieldMessage(jss::limit, "unsigned integer")); BEAST_EXPECT(lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0"); BEAST_EXPECT(lines.isMember(jss::ripplerpc) && lines[jss::ripplerpc] == "2.0"); BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5); @@ -1090,7 +1090,7 @@ public: auto const linesD = env.rpc("json2", to_string(requestD)); BEAST_EXPECT( linesD[jss::error][jss::message] == - RPC::makeError(RpcInvalidParams)[jss::error_message]); + rpc::makeError(RpcInvalidParams)[jss::error_message]); BEAST_EXPECT(linesD.isMember(jss::jsonrpc) && linesD[jss::jsonrpc] == "2.0"); BEAST_EXPECT(linesD.isMember(jss::ripplerpc) && linesD[jss::ripplerpc] == "2.0"); BEAST_EXPECT(linesD.isMember(jss::id) && linesD[jss::id] == 5); @@ -1109,7 +1109,7 @@ public: auto const lines = env.rpc("json2", to_string(request)); BEAST_EXPECT( lines[jss::error][jss::message] == - RPC::expectedFieldMessage(jss::marker, "string")); + rpc::expectedFieldMessage(jss::marker, "string")); BEAST_EXPECT(lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0"); BEAST_EXPECT(lines.isMember(jss::ripplerpc) && lines[jss::ripplerpc] == "2.0"); BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5); @@ -1266,7 +1266,7 @@ public: auto const linesEnd = env.rpc("json2", to_string(linesEndRequest)); BEAST_EXPECT( linesEnd[jss::error][jss::message] == - RPC::makeError(RpcInvalidParams)[jss::error_message]); + rpc::makeError(RpcInvalidParams)[jss::error_message]); BEAST_EXPECT(linesEnd.isMember(jss::jsonrpc) && linesEnd[jss::jsonrpc] == "2.0"); BEAST_EXPECT(linesEnd.isMember(jss::ripplerpc) && linesEnd[jss::ripplerpc] == "2.0"); BEAST_EXPECT(linesEnd.isMember(jss::id) && linesEnd[jss::id] == 5); @@ -1286,4 +1286,4 @@ public: BEAST_DEFINE_TESTSUITE(AccountLines, rpc, xrpl); -} // namespace xrpl::RPC +} // namespace xrpl::rpc diff --git a/src/test/rpc/AccountTx_test.cpp b/src/test/rpc/AccountTx_test.cpp index f1fbc2871b..b144a6dc84 100644 --- a/src/test/rpc/AccountTx_test.cpp +++ b/src/test/rpc/AccountTx_test.cpp @@ -199,7 +199,7 @@ class AccountTx_test : public beast::unit_test::Suite auto isErr = [](json::Value const& j, ErrorCodeI code) { return j.isMember(jss::result) && j[jss::result].isMember(jss::error) && - j[jss::result][jss::error] == RPC::getErrorInfo(code).token; + j[jss::result][jss::error] == rpc::getErrorInfo(code).token; }; json::Value jParams; @@ -425,56 +425,56 @@ class AccountTx_test : public beast::unit_test::Suite p[jss::limit] = 1.2; BEAST_EXPECT( env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] == - RPC::expectedFieldMessage(jss::limit, "unsigned integer")); + rpc::expectedFieldMessage(jss::limit, "unsigned integer")); // Test case: limit = "10" should fail (string instead of integer) p[jss::limit] = "10"; BEAST_EXPECT( env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] == - RPC::expectedFieldMessage(jss::limit, "unsigned integer")); + rpc::expectedFieldMessage(jss::limit, "unsigned integer")); // Test case: limit = true should fail (boolean instead of integer) p[jss::limit] = true; BEAST_EXPECT( env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] == - RPC::expectedFieldMessage(jss::limit, "unsigned integer")); + rpc::expectedFieldMessage(jss::limit, "unsigned integer")); // Test case: limit = false should fail (boolean instead of integer) p[jss::limit] = false; BEAST_EXPECT( env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] == - RPC::expectedFieldMessage(jss::limit, "unsigned integer")); + rpc::expectedFieldMessage(jss::limit, "unsigned integer")); // Test case: limit = -1 should fail (negative number) p[jss::limit] = -1; BEAST_EXPECT( env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] == - RPC::expectedFieldMessage(jss::limit, "unsigned integer")); + rpc::expectedFieldMessage(jss::limit, "unsigned integer")); // Test case: limit = [] should fail (array instead of integer) p[jss::limit] = json::Value(json::ValueType::Array); BEAST_EXPECT( env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] == - RPC::expectedFieldMessage(jss::limit, "unsigned integer")); + rpc::expectedFieldMessage(jss::limit, "unsigned integer")); // Test case: limit = {} should fail (object instead of integer) p[jss::limit] = json::Value(json::ValueType::Object); BEAST_EXPECT( env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] == - RPC::expectedFieldMessage(jss::limit, "unsigned integer")); + rpc::expectedFieldMessage(jss::limit, "unsigned integer")); // Test case: limit = "malformed" should fail (malformed string) p[jss::limit] = "malformed"; BEAST_EXPECT( env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] == - RPC::expectedFieldMessage(jss::limit, "unsigned integer")); + rpc::expectedFieldMessage(jss::limit, "unsigned integer")); // Test case: limit = ["limit"] should fail (array with string) p[jss::limit] = json::Value(json::ValueType::Array); p[jss::limit].append("limit"); BEAST_EXPECT( env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] == - RPC::expectedFieldMessage(jss::limit, "unsigned integer")); + rpc::expectedFieldMessage(jss::limit, "unsigned integer")); // Test case: limit = {"limit": 10} should fail (object with // property) @@ -482,7 +482,7 @@ class AccountTx_test : public beast::unit_test::Suite p[jss::limit][jss::limit] = 10; BEAST_EXPECT( env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] == - RPC::expectedFieldMessage(jss::limit, "unsigned integer")); + rpc::expectedFieldMessage(jss::limit, "unsigned integer")); // Test case: limit = 10 should succeed (valid integer) p[jss::limit] = 10; diff --git a/src/test/rpc/Book_test.cpp b/src/test/rpc/Book_test.cpp index 83f7b64b4b..646cf190ff 100644 --- a/src/test/rpc/Book_test.cpp +++ b/src/test/rpc/Book_test.cpp @@ -1548,7 +1548,7 @@ public: auto usd = gw["USD"]; - for (auto i = 0; i <= RPC::Tuning::kBookOffers.rmax; i++) + for (auto i = 0; i <= rpc::tuning::kBookOffers.rmax; i++) env(offer(gw, XRP(50 + (1 * i)), usd(1.0 + (0.1 * i)))); if (asAdmin) @@ -1565,15 +1565,15 @@ public: BEAST_EXPECT(jrr[jss::offers].size() == (asAdmin ? 1u : 0u)); // NOTE - a marker field is not returned for this method - jvParams[jss::limit] = RPC::Tuning::kBookOffers.rmax + 1; + jvParams[jss::limit] = rpc::tuning::kBookOffers.rmax + 1; jrr = env.rpc("json", "book_offers", to_string(jvParams))[jss::result]; BEAST_EXPECT(jrr[jss::offers].isArray()); - BEAST_EXPECT(jrr[jss::offers].size() == (asAdmin ? RPC::Tuning::kBookOffers.rmax + 1 : 0u)); + BEAST_EXPECT(jrr[jss::offers].size() == (asAdmin ? rpc::tuning::kBookOffers.rmax + 1 : 0u)); jvParams[jss::limit] = json::ValueType::Null; jrr = env.rpc("json", "book_offers", to_string(jvParams))[jss::result]; BEAST_EXPECT(jrr[jss::offers].isArray()); - BEAST_EXPECT(jrr[jss::offers].size() == (asAdmin ? RPC::Tuning::kBookOffers.rDefault : 0u)); + BEAST_EXPECT(jrr[jss::offers].size() == (asAdmin ? rpc::tuning::kBookOffers.rDefault : 0u)); } void diff --git a/src/test/rpc/Handler_test.cpp b/src/test/rpc/Handler_test.cpp index e900b92fc3..be78864cac 100644 --- a/src/test/rpc/Handler_test.cpp +++ b/src/test/rpc/Handler_test.cpp @@ -88,7 +88,7 @@ class Handler_test : public beast::unit_test::Suite std::random_device dev; std::ranlux48 prng(dev()); - std::vector names = test::jtx::makeVector(xrpl::RPC::getHandlerNames()); + std::vector names = test::jtx::makeVector(xrpl::rpc::getHandlerNames()); std::uniform_int_distribution distr{0, names.size() - 1}; @@ -96,7 +96,7 @@ class Handler_test : public beast::unit_test::Suite auto const [mean, stdev, n] = time( 1'000'000, [&](std::size_t i) { - auto const d = RPC::getHandler(1, false, names[i]); + auto const d = rpc::getHandler(1, false, names[i]); dummy = dummy + i + (int)d->role; }, [&]() -> std::size_t { return distr(prng); }); diff --git a/src/test/rpc/JSONRPC_test.cpp b/src/test/rpc/JSONRPC_test.cpp index e18974e7e7..efba4075c7 100644 --- a/src/test/rpc/JSONRPC_test.cpp +++ b/src/test/rpc/JSONRPC_test.cpp @@ -36,7 +36,7 @@ #include #include -namespace xrpl::RPC { +namespace xrpl::rpc { struct TxnTestData { @@ -2248,7 +2248,7 @@ public: env.app().getTxQ(), env.app()); - BEAST_EXPECT(!RPC::containsError(result)); + BEAST_EXPECT(!rpc::containsError(result)); BEAST_EXPECT( req[jss::tx_json].isMember(jss::Fee) && req[jss::tx_json][jss::Fee] == baseFee); } @@ -2268,7 +2268,7 @@ public: env.app().getTxQ(), env.app()); - BEAST_EXPECT(!RPC::containsError(result)); + BEAST_EXPECT(!rpc::containsError(result)); BEAST_EXPECT( req[jss::tx_json].isMember(jss::Fee) && req[jss::tx_json][jss::Fee] == baseFee); } @@ -2285,7 +2285,7 @@ public: env.app().getTxQ(), env.app()); - BEAST_EXPECT(RPC::containsError(result)); + BEAST_EXPECT(rpc::containsError(result)); BEAST_EXPECT(!req[jss::tx_json].isMember(jss::Fee)); } @@ -2306,7 +2306,7 @@ public: env.app().getTxQ(), env.app()); - BEAST_EXPECT(RPC::containsError(result)); + BEAST_EXPECT(rpc::containsError(result)); BEAST_EXPECT(!req[jss::tx_json].isMember(jss::Fee)); } @@ -2325,7 +2325,7 @@ public: env.app().getTxQ(), env.app()); - BEAST_EXPECT(RPC::containsError(result)); + BEAST_EXPECT(rpc::containsError(result)); BEAST_EXPECT(!req[jss::tx_json].isMember(jss::Fee)); } @@ -2344,7 +2344,7 @@ public: env.app().getTxQ(), env.app()); - BEAST_EXPECT(RPC::containsError(result)); + BEAST_EXPECT(rpc::containsError(result)); BEAST_EXPECT(!req[jss::tx_json].isMember(jss::Fee)); } @@ -2400,7 +2400,7 @@ public: env.app().getTxQ(), env.app()); - BEAST_EXPECT(!RPC::containsError(result)); + BEAST_EXPECT(!rpc::containsError(result)); BEAST_EXPECT(req[jss::tx_json].isMember(jss::Fee) && req[jss::tx_json][jss::Fee] == 10); } @@ -2422,7 +2422,7 @@ public: env.app().getTxQ(), env.app()); - BEAST_EXPECT(!RPC::containsError(result)); + BEAST_EXPECT(!rpc::containsError(result)); BEAST_EXPECT(req[jss::tx_json].isMember(jss::Fee) && req[jss::tx_json][jss::Fee] == 10); } @@ -2450,7 +2450,7 @@ public: env.app().getTxQ(), env.app()); - BEAST_EXPECT(!RPC::containsError(result)); + BEAST_EXPECT(!rpc::containsError(result)); BEAST_EXPECT( req[jss::tx_json].isMember(jss::Fee) && req[jss::tx_json][jss::Fee] == 8889); } @@ -2473,7 +2473,7 @@ public: env.app().getTxQ(), env.app()); - BEAST_EXPECT(RPC::containsError(result)); + BEAST_EXPECT(rpc::containsError(result)); BEAST_EXPECT(!req[jss::tx_json].isMember(jss::Fee)); } @@ -2496,7 +2496,7 @@ public: env.app().getTxQ(), env.app()); - BEAST_EXPECT(RPC::containsError(result)); + BEAST_EXPECT(rpc::containsError(result)); BEAST_EXPECT(!req[jss::tx_json].isMember(jss::Fee)); } @@ -2519,7 +2519,7 @@ public: env.app().getTxQ(), env.app()); - BEAST_EXPECT(!RPC::containsError(result)); + BEAST_EXPECT(!rpc::containsError(result)); BEAST_EXPECT( req[jss::tx_json].isMember(jss::Fee) && req[jss::tx_json][jss::Fee] == 8889); } @@ -2542,7 +2542,7 @@ public: env.app().getTxQ(), env.app()); - BEAST_EXPECT(RPC::containsError(result)); + BEAST_EXPECT(rpc::containsError(result)); } { @@ -2563,7 +2563,7 @@ public: env.app().getTxQ(), env.app()); - BEAST_EXPECT(RPC::containsError(result)); + BEAST_EXPECT(rpc::containsError(result)); } { @@ -2585,7 +2585,7 @@ public: env.app().getTxQ(), env.app()); - BEAST_EXPECT(RPC::containsError(result)); + BEAST_EXPECT(rpc::containsError(result)); } env.close(); @@ -2598,7 +2598,7 @@ public: auto rpcResult = env.rpc("json", "sign", to_string(toSign)); auto result = rpcResult[jss::result]; - BEAST_EXPECT(!RPC::containsError(result)); + BEAST_EXPECT(!rpc::containsError(result)); BEAST_EXPECT( result[jss::tx_json].isMember(jss::Fee) && result[jss::tx_json][jss::Fee] == "10"); BEAST_EXPECT( @@ -2624,7 +2624,7 @@ public: auto rpcResult = env.rpc("json", "sign", to_string(toSign)); auto result = rpcResult[jss::result]; - BEAST_EXPECT(!RPC::containsError(result)); + BEAST_EXPECT(!rpc::containsError(result)); BEAST_EXPECT( result[jss::tx_json].isMember(jss::Fee) && result[jss::tx_json][jss::Fee] == "7813"); @@ -2651,7 +2651,7 @@ public: auto rpcResult = env.rpc("json", "sign", to_string(toSign)); auto result = rpcResult[jss::result]; - BEAST_EXPECT(!RPC::containsError(result)); + BEAST_EXPECT(!rpc::containsError(result)); BEAST_EXPECT( result[jss::tx_json].isMember(jss::Fee) && result[jss::tx_json][jss::Fee] == "47"); BEAST_EXPECT( @@ -2682,7 +2682,7 @@ public: auto rpcResult = env.rpc("json", "sign", to_string(toSign)); auto result = rpcResult[jss::result]; - BEAST_EXPECT(!RPC::containsError(result)); + BEAST_EXPECT(!rpc::containsError(result)); BEAST_EXPECT( result[jss::tx_json].isMember(jss::Fee) && result[jss::tx_json][jss::Fee] == "6806"); @@ -2711,7 +2711,7 @@ public: auto rpcResult = env.rpc("json", "sign", to_string(toSign)); auto result = rpcResult[jss::result]; - BEAST_EXPECT(!RPC::containsError(result)); + BEAST_EXPECT(!rpc::containsError(result)); BEAST_EXPECT( result[jss::tx_json].isMember(jss::NetworkID) && result[jss::tx_json][jss::NetworkID] == 1025); @@ -2791,7 +2791,7 @@ public: { json::Value req; json::Reader().parse(txnTest.json, req); - if (RPC::containsError(req)) + if (rpc::containsError(req)) Throw("Internal JSONRPC_test error. Bad test JSON."); static Role const kTestedRoles[] = { @@ -2815,7 +2815,7 @@ public: } std::string errStr; - if (RPC::containsError(result)) + if (rpc::containsError(result)) errStr = result["error_message"].asString(); if (errStr == txnTest.expMsg[get<3>(testFunc)]) @@ -2848,4 +2848,4 @@ public: BEAST_DEFINE_TESTSUITE(JSONRPC, rpc, xrpl); -} // namespace xrpl::RPC +} // namespace xrpl::rpc diff --git a/src/test/rpc/KeyGeneration_test.cpp b/src/test/rpc/KeyGeneration_test.cpp index aafe6f75a5..2b056fc6d2 100644 --- a/src/test/rpc/KeyGeneration_test.cpp +++ b/src/test/rpc/KeyGeneration_test.cpp @@ -15,7 +15,7 @@ #include #include -namespace xrpl::RPC { +namespace xrpl::rpc { struct KeyStrings { @@ -800,4 +800,4 @@ public: BEAST_DEFINE_TESTSUITE(WalletPropose, rpc, xrpl); -} // namespace xrpl::RPC +} // namespace xrpl::rpc diff --git a/src/test/rpc/LedgerEntry_test.cpp b/src/test/rpc/LedgerEntry_test.cpp index 7adb5a4518..b8301d5656 100644 --- a/src/test/rpc/LedgerEntry_test.cpp +++ b/src/test/rpc/LedgerEntry_test.cpp @@ -349,7 +349,7 @@ class LedgerEntry_test : public beast::unit_test::Suite json::Value const jrr = env.rpc( apiVersion, "json", "ledger_entry", to_string(correctRequest))[jss::result]; auto const expectedErrMsg = - RPC::expectedFieldMessage(fieldName, getTypeName(typeID)); + rpc::expectedFieldMessage(fieldName, getTypeName(typeID)); checkErrorValue(jrr, expectedError, expectedErrMsg, location); }; @@ -383,13 +383,13 @@ class LedgerEntry_test : public beast::unit_test::Suite json::Value const jrr = env.rpc( apiVersion, "json", "ledger_entry", to_string(correctRequest))[jss::result]; checkErrorValue( - jrr, "malformedRequest", RPC::missingFieldMessage(fieldName.cStr()), location); + jrr, "malformedRequest", rpc::missingFieldMessage(fieldName.cStr()), location); correctRequest[parentFieldName][fieldName] = json::ValueType::Null; json::Value const jrr2 = env.rpc( apiVersion, "json", "ledger_entry", to_string(correctRequest))[jss::result]; checkErrorValue( - jrr2, "malformedRequest", RPC::missingFieldMessage(fieldName.cStr()), location); + jrr2, "malformedRequest", rpc::missingFieldMessage(fieldName.cStr()), location); } auto tryField = [&](json::Value fieldValue) -> void { correctRequest[parentFieldName][fieldName] = fieldValue; @@ -399,7 +399,7 @@ class LedgerEntry_test : public beast::unit_test::Suite checkErrorValue( jrr, expectedError, - RPC::expectedFieldMessage(fieldName, getTypeName(typeID)), + rpc::expectedFieldMessage(fieldName, getTypeName(typeID)), location); }; @@ -1083,8 +1083,8 @@ class LedgerEntry_test : public beast::unit_test::Suite json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result]; auto const expectedErrMsg = fieldValue.isNull() - ? RPC::missingFieldMessage(jss::issuer.cStr()) - : RPC::expectedFieldMessage(jss::issuer, "AccountID"); + ? rpc::missingFieldMessage(jss::issuer.cStr()) + : rpc::expectedFieldMessage(jss::issuer, "AccountID"); checkErrorValue(jrr, "malformedAuthorizedCredentials", expectedErrMsg); }; @@ -1114,7 +1114,7 @@ class LedgerEntry_test : public beast::unit_test::Suite checkErrorValue( jrr[jss::result], "malformedAuthorizedCredentials", - RPC::expectedFieldMessage(jss::authorized_credentials, "array")); + rpc::expectedFieldMessage(jss::authorized_credentials, "array")); } { @@ -1134,8 +1134,8 @@ class LedgerEntry_test : public beast::unit_test::Suite json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result]; auto const expectedErrMsg = fieldValue.isNull() - ? RPC::missingFieldMessage(jss::credential_type.cStr()) - : RPC::expectedFieldMessage(jss::credential_type, "hex string"); + ? rpc::missingFieldMessage(jss::credential_type.cStr()) + : rpc::expectedFieldMessage(jss::credential_type, "hex string"); checkErrorValue(jrr, "malformedAuthorizedCredentials", expectedErrMsg); }; @@ -1836,7 +1836,7 @@ class LedgerEntry_test : public beast::unit_test::Suite checkErrorValue( jrr, "malformedAddress", - RPC::expectedFieldMessage(jss::accounts, "array of Accounts")); + rpc::expectedFieldMessage(jss::accounts, "array of Accounts")); } { @@ -1851,7 +1851,7 @@ class LedgerEntry_test : public beast::unit_test::Suite checkErrorValue( jrr, "malformedAddress", - RPC::expectedFieldMessage(jss::accounts, "array of Accounts")); + rpc::expectedFieldMessage(jss::accounts, "array of Accounts")); } }; diff --git a/src/test/rpc/LedgerRPC_test.cpp b/src/test/rpc/LedgerRPC_test.cpp index 3a2c957691..af93108ff2 100644 --- a/src/test/rpc/LedgerRPC_test.cpp +++ b/src/test/rpc/LedgerRPC_test.cpp @@ -158,7 +158,7 @@ class LedgerRPC_test : public beast::unit_test::Suite { // Request a ledger with a very large (double) sequence. auto const ret = env.rpc("json", "ledger", "{ \"ledger_index\" : 2e15 }"); - BEAST_EXPECT(RPC::containsError(ret)); + BEAST_EXPECT(rpc::containsError(ret)); BEAST_EXPECT(ret[jss::error_message] == "Invalid parameters."); } diff --git a/src/test/rpc/LedgerRequest_test.cpp b/src/test/rpc/LedgerRequest_test.cpp index 93feee9497..98bde4e5a5 100644 --- a/src/test/rpc/LedgerRequest_test.cpp +++ b/src/test/rpc/LedgerRequest_test.cpp @@ -15,7 +15,7 @@ #include #include -namespace xrpl::RPC { +namespace xrpl::rpc { class LedgerRequest_test : public beast::unit_test::Suite { @@ -43,28 +43,28 @@ public: // arbitrary text is converted to 0. auto const result = env.rpc("ledger_request", "arbitrary_text"); BEAST_EXPECT( - RPC::containsError(result[jss::result]) && + rpc::containsError(result[jss::result]) && result[jss::result][jss::error_message] == "Ledger index too small"); } { auto const result = env.rpc("ledger_request", "-1"); BEAST_EXPECT( - RPC::containsError(result[jss::result]) && + rpc::containsError(result[jss::result]) && result[jss::result][jss::error_message] == "Ledger index too small"); } { auto const result = env.rpc("ledger_request", "0"); BEAST_EXPECT( - RPC::containsError(result[jss::result]) && + rpc::containsError(result[jss::result]) && result[jss::result][jss::error_message] == "Ledger index too small"); } { auto const result = env.rpc("ledger_request", "1"); BEAST_EXPECT( - !RPC::containsError(result[jss::result]) && + !rpc::containsError(result[jss::result]) && result[jss::result][jss::ledger_index] == 1 && result[jss::result].isMember(jss::ledger)); BEAST_EXPECT( @@ -75,7 +75,7 @@ public: { auto const result = env.rpc("ledger_request", "2"); BEAST_EXPECT( - !RPC::containsError(result[jss::result]) && + !rpc::containsError(result[jss::result]) && result[jss::result][jss::ledger_index] == 2 && result[jss::result].isMember(jss::ledger)); BEAST_EXPECT( @@ -86,7 +86,7 @@ public: { auto const result = env.rpc("ledger_request", "3"); BEAST_EXPECT( - !RPC::containsError(result[jss::result]) && + !rpc::containsError(result[jss::result]) && result[jss::result][jss::ledger_index] == 3 && result[jss::result].isMember(jss::ledger)); BEAST_EXPECT( @@ -98,7 +98,7 @@ public: { auto const r = env.rpc("ledger_request", ledgerHash); BEAST_EXPECT( - !RPC::containsError(r[jss::result]) && r[jss::result][jss::ledger_index] == 3 && + !rpc::containsError(r[jss::result]) && r[jss::result][jss::ledger_index] == 3 && r[jss::result].isMember(jss::ledger)); BEAST_EXPECT( r[jss::result][jss::ledger].isMember(jss::ledger_hash) && @@ -112,7 +112,7 @@ public: auto const result = env.rpc("ledger_request", ledgerHash); BEAST_EXPECT( - RPC::containsError(result[jss::result]) && + rpc::containsError(result[jss::result]) && result[jss::result][jss::error_message] == "Invalid field 'ledger_hash', not hex string."); } @@ -123,21 +123,21 @@ public: auto const result = env.rpc("ledger_request", ledgerHash); BEAST_EXPECT( - !RPC::containsError(result[jss::result]) && + !rpc::containsError(result[jss::result]) && result[jss::result][jss::have_header] == false); } { auto const result = env.rpc("ledger_request", "4"); BEAST_EXPECT( - RPC::containsError(result[jss::result]) && + rpc::containsError(result[jss::result]) && result[jss::result][jss::error_message] == "Ledger index too large"); } { auto const result = env.rpc("ledger_request", "5"); BEAST_EXPECT( - RPC::containsError(result[jss::result]) && + rpc::containsError(result[jss::result]) && result[jss::result][jss::error_message] == "Ledger index too large"); } } @@ -357,4 +357,4 @@ public: BEAST_DEFINE_TESTSUITE(LedgerRequest, rpc, xrpl); -} // namespace xrpl::RPC +} // namespace xrpl::rpc diff --git a/src/test/rpc/NoRippleCheck_test.cpp b/src/test/rpc/NoRippleCheck_test.cpp index 9c17d291a1..6e30f944c7 100644 --- a/src/test/rpc/NoRippleCheck_test.cpp +++ b/src/test/rpc/NoRippleCheck_test.cpp @@ -286,9 +286,9 @@ class NoRippleCheckLimits_test : public beast::unit_test::Suite // be better if we could add this functionality to Env somehow // or otherwise disable endpoint charging for certain test // cases. - using namespace xrpl::Resource; + using namespace xrpl::resource; using namespace std::chrono; - using namespace beast::IP; + using namespace beast::ip; auto c = env.app().getResourceManager().newInboundEndpoint( Endpoint::fromString(test::getEnvLocalhostAddr())); @@ -301,7 +301,7 @@ class NoRippleCheckLimits_test : public beast::unit_test::Suite } }; - for (auto i = 0; i < xrpl::RPC::Tuning::kNoRippleCheck.rmax + 5; ++i) + for (auto i = 0; i < xrpl::rpc::tuning::kNoRippleCheck.rmax + 5; ++i) { if (!admin) checkBalance(); diff --git a/src/test/rpc/RPCCall_test.cpp b/src/test/rpc/RPCCall_test.cpp index 4b5ab1f230..ef3213008c 100644 --- a/src/test/rpc/RPCCall_test.cpp +++ b/src/test/rpc/RPCCall_test.cpp @@ -5855,8 +5855,8 @@ public: { testcase << "RPCCall API version " << apiVersion; if (!BEAST_EXPECT( - apiVersion >= RPC::kApiMinimumSupportedVersion && - apiVersion <= RPC::kApiMaximumValidVersion)) + apiVersion >= rpc::kApiMinimumSupportedVersion && + apiVersion <= rpc::kApiMaximumValidVersion)) return; test::jtx::Env const env(*this, makeNetworkConfig(11111)); // Used only for its Journal. @@ -5870,8 +5870,8 @@ public: std::vector const args{rpcCallTest.args.begin(), rpcCallTest.args.end()}; char const* const expVersioned = - (apiVersion - RPC::kApiMinimumSupportedVersion) < rpcCallTest.exp.size() - ? rpcCallTest.exp[apiVersion - RPC::kApiMinimumSupportedVersion] + (apiVersion - rpc::kApiMinimumSupportedVersion) < rpcCallTest.exp.size() + ? rpcCallTest.exp[apiVersion - rpc::kApiMinimumSupportedVersion] : rpcCallTest.exp.back(); // Note that, over the long term, kNone of these tests should diff --git a/src/test/rpc/RPCHelpers_test.cpp b/src/test/rpc/RPCHelpers_test.cpp index 1458c0aa80..25368235a3 100644 --- a/src/test/rpc/RPCHelpers_test.cpp +++ b/src/test/rpc/RPCHelpers_test.cpp @@ -19,50 +19,50 @@ public: // Test no type. json::Value tx = json::ValueType::Object; - auto result = RPC::chooseLedgerEntryType(tx); - BEAST_EXPECT(result.first == RPC::Status::kOK); + auto result = rpc::chooseLedgerEntryType(tx); + BEAST_EXPECT(result.first == rpc::Status::kOK); BEAST_EXPECT(result.second == 0); // Test empty type. tx[jss::type] = ""; - result = RPC::chooseLedgerEntryType(tx); - BEAST_EXPECT(result.first == RPC::Status{RpcInvalidParams}); + result = rpc::chooseLedgerEntryType(tx); + BEAST_EXPECT(result.first == rpc::Status{RpcInvalidParams}); BEAST_EXPECT(result.second == 0); // Test type using canonical name in mixedcase. tx[jss::type] = "MPTokenIssuance"; - result = RPC::chooseLedgerEntryType(tx); - BEAST_EXPECT(result.first == RPC::Status::kOK); + result = rpc::chooseLedgerEntryType(tx); + BEAST_EXPECT(result.first == rpc::Status::kOK); BEAST_EXPECT(result.second == ltMPTOKEN_ISSUANCE); // Test type using canonical name in lowercase. tx[jss::type] = "mptokenissuance"; - result = RPC::chooseLedgerEntryType(tx); - BEAST_EXPECT(result.first == RPC::Status::kOK); + result = rpc::chooseLedgerEntryType(tx); + BEAST_EXPECT(result.first == rpc::Status::kOK); BEAST_EXPECT(result.second == ltMPTOKEN_ISSUANCE); // Test type using RPC name with exact match. tx[jss::type] = "mpt_issuance"; - result = RPC::chooseLedgerEntryType(tx); - BEAST_EXPECT(result.first == RPC::Status::kOK); + result = rpc::chooseLedgerEntryType(tx); + BEAST_EXPECT(result.first == rpc::Status::kOK); BEAST_EXPECT(result.second == ltMPTOKEN_ISSUANCE); // Test type using RPC name with inexact match. tx[jss::type] = "MPT_Issuance"; - result = RPC::chooseLedgerEntryType(tx); - BEAST_EXPECT(result.first == RPC::Status{RpcInvalidParams}); + result = rpc::chooseLedgerEntryType(tx); + BEAST_EXPECT(result.first == rpc::Status{RpcInvalidParams}); BEAST_EXPECT(result.second == 0); // Test invalid type. tx[jss::type] = 1234; - result = RPC::chooseLedgerEntryType(tx); - BEAST_EXPECT(result.first == RPC::Status{RpcInvalidParams}); + result = rpc::chooseLedgerEntryType(tx); + BEAST_EXPECT(result.first == rpc::Status{RpcInvalidParams}); BEAST_EXPECT(result.second == 0); // Test unknown type. tx[jss::type] = "unknown"; - result = RPC::chooseLedgerEntryType(tx); - BEAST_EXPECT(result.first == RPC::Status{RpcInvalidParams}); + result = rpc::chooseLedgerEntryType(tx); + BEAST_EXPECT(result.first == rpc::Status{RpcInvalidParams}); BEAST_EXPECT(result.second == 0); } diff --git a/src/test/rpc/Status_test.cpp b/src/test/rpc/Status_test.cpp index c4f8544980..aaf696e9af 100644 --- a/src/test/rpc/Status_test.cpp +++ b/src/test/rpc/Status_test.cpp @@ -12,7 +12,7 @@ #include #include -namespace xrpl::RPC { +namespace xrpl::rpc { class codeString_test : public beast::unit_test::Suite { @@ -202,6 +202,6 @@ public: } }; -BEAST_DEFINE_TESTSUITE(fillJson, rpc, RPC); +BEAST_DEFINE_TESTSUITE(fillJson, rpc, xrpl); -} // namespace xrpl::RPC +} // namespace xrpl::rpc diff --git a/src/test/rpc/TransactionEntry_test.cpp b/src/test/rpc/TransactionEntry_test.cpp index 38a95e84f8..b57c615b71 100644 --- a/src/test/rpc/TransactionEntry_test.cpp +++ b/src/test/rpc/TransactionEntry_test.cpp @@ -183,7 +183,7 @@ class TransactionEntry_test : public beast::unit_test::Suite { json::Value expected; json::Reader().parse(expectedJson, expected); - if (RPC::containsError(expected)) + if (rpc::containsError(expected)) Throw("Internal JSONRPC_test error. Bad test JSON."); for (auto memberIt = expected.begin(); memberIt != expected.end(); memberIt++) diff --git a/src/test/rpc/Transaction_test.cpp b/src/test/rpc/Transaction_test.cpp index 4dae475b63..2921c63c17 100644 --- a/src/test/rpc/Transaction_test.cpp +++ b/src/test/rpc/Transaction_test.cpp @@ -62,9 +62,9 @@ class Transaction_test : public beast::unit_test::Suite char const* command = jss::tx.cStr(); char const* binary = jss::binary.cStr(); - char const* notFound = RPC::getErrorInfo(RpcTxnNotFound).token; - char const* invalid = RPC::getErrorInfo(RpcInvalidLgrRange).token; - char const* excessive = RPC::getErrorInfo(RpcExcessiveLgrRange).token; + char const* notFound = rpc::getErrorInfo(RpcTxnNotFound).token; + char const* invalid = rpc::getErrorInfo(RpcInvalidLgrRange).token; + char const* excessive = rpc::getErrorInfo(RpcExcessiveLgrRange).token; Env env{*this, features}; auto const alice = Account("alice"); @@ -301,9 +301,9 @@ class Transaction_test : public beast::unit_test::Suite char const* command = jss::tx.cStr(); char const* binary = jss::binary.cStr(); - char const* notFound = RPC::getErrorInfo(RpcTxnNotFound).token; - char const* invalid = RPC::getErrorInfo(RpcInvalidLgrRange).token; - char const* excessive = RPC::getErrorInfo(RpcExcessiveLgrRange).token; + char const* notFound = rpc::getErrorInfo(RpcTxnNotFound).token; + char const* invalid = rpc::getErrorInfo(RpcInvalidLgrRange).token; + char const* excessive = rpc::getErrorInfo(RpcExcessiveLgrRange).token; Env env{*this, makeNetworkConfig(11111)}; uint32_t const netID = env.app().getNetworkIDService().getNetworkID(); @@ -333,7 +333,7 @@ class Transaction_test : public beast::unit_test::Suite auto const result = env.rpc( command, // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - *RPC::encodeCTID(startLegSeq + i, txnIdx, netID), + *rpc::encodeCTID(startLegSeq + i, txnIdx, netID), binary, to_string(startLegSeq), to_string(endLegSeq)); @@ -345,7 +345,7 @@ class Transaction_test : public beast::unit_test::Suite auto const tx = env.jt(noop(alice), Seq(env.seq(alice))).stx; // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - auto const ctid = *RPC::encodeCTID(endLegSeq, tx->getSeqValue(), netID); + auto const ctid = *rpc::encodeCTID(endLegSeq, tx->getSeqValue(), netID); for (int deltaEndSeq = 0; deltaEndSeq < 2; ++deltaEndSeq) { auto const result = env.rpc( @@ -374,7 +374,7 @@ class Transaction_test : public beast::unit_test::Suite auto const result = env.rpc( command, // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - *RPC::encodeCTID(startLegSeq + i, txnIdx, netID), + *rpc::encodeCTID(startLegSeq + i, txnIdx, netID), binary, to_string(endLegSeq + 1), to_string(endLegSeq + 100)); @@ -434,7 +434,7 @@ class Transaction_test : public beast::unit_test::Suite auto const result = env.rpc( command, // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - *RPC::encodeCTID(endLegSeq, txnIdx, netID), + *rpc::encodeCTID(endLegSeq, txnIdx, netID), to_string(startLegSeq), to_string(deletedLedger - 1)); @@ -527,75 +527,75 @@ class Transaction_test : public beast::unit_test::Suite // Test case 1: Valid input values auto const expected11 = std::optional("CFFFFFFFFFFFFFFF"); - BEAST_EXPECT(RPC::encodeCTID(0x0FFF'FFFFUL, 0xFFFFU, 0xFFFFU) == expected11); + BEAST_EXPECT(rpc::encodeCTID(0x0FFF'FFFFUL, 0xFFFFU, 0xFFFFU) == expected11); auto const expected12 = std::optional("C000000000000000"); - BEAST_EXPECT(RPC::encodeCTID(0, 0, 0) == expected12); + BEAST_EXPECT(rpc::encodeCTID(0, 0, 0) == expected12); auto const expected13 = std::optional("C000000100020003"); - BEAST_EXPECT(RPC::encodeCTID(1U, 2U, 3U) == expected13); + BEAST_EXPECT(rpc::encodeCTID(1U, 2U, 3U) == expected13); auto const expected14 = std::optional("C0CA2AA7326FFFFF"); - BEAST_EXPECT(RPC::encodeCTID(13249191UL, 12911U, 65535U) == expected14); + BEAST_EXPECT(rpc::encodeCTID(13249191UL, 12911U, 65535U) == expected14); // Test case 2: ledger_seq greater than 0xFFFFFFF - BEAST_EXPECT(!RPC::encodeCTID(0x1000'0000UL, 0xFFFFU, 0xFFFFU)); + BEAST_EXPECT(!rpc::encodeCTID(0x1000'0000UL, 0xFFFFU, 0xFFFFU)); // Test case 3: txn_index greater than 0xFFFF - BEAST_EXPECT(!RPC::encodeCTID(0x0FFF'FFFF, 0x1'0000, 0xFFFF)); + BEAST_EXPECT(!rpc::encodeCTID(0x0FFF'FFFF, 0x1'0000, 0xFFFF)); // Test case 4: network_id greater than 0xFFFF - BEAST_EXPECT(!RPC::encodeCTID(0x0FFF'FFFFUL, 0xFFFFU, 0x1'0000U)); + BEAST_EXPECT(!rpc::encodeCTID(0x0FFF'FFFFUL, 0xFFFFU, 0x1'0000U)); // Test case 5: Valid input values auto const expected51 = std::optional>(std::make_tuple(0, 0, 0)); - BEAST_EXPECT(RPC::decodeCTID("C000000000000000") == expected51); + BEAST_EXPECT(rpc::decodeCTID("C000000000000000") == expected51); auto const expected52 = std::optional>(std::make_tuple(1U, 2U, 3U)); - BEAST_EXPECT(RPC::decodeCTID("C000000100020003") == expected52); + BEAST_EXPECT(rpc::decodeCTID("C000000100020003") == expected52); auto const expected53 = std::optional>( std::make_tuple(13249191UL, 12911U, 49221U)); - BEAST_EXPECT(RPC::decodeCTID("C0CA2AA7326FC045") == expected53); + BEAST_EXPECT(rpc::decodeCTID("C0CA2AA7326FC045") == expected53); // Test case 6: ctid not a string or big int - BEAST_EXPECT(!RPC::decodeCTID(0xCFF)); + BEAST_EXPECT(!rpc::decodeCTID(0xCFF)); // Test case 7: ctid not a hexadecimal string - BEAST_EXPECT(!RPC::decodeCTID("C003FFFFFFFFFFFG")); + BEAST_EXPECT(!rpc::decodeCTID("C003FFFFFFFFFFFG")); // Test case 8: ctid not exactly 16 nibbles - BEAST_EXPECT(!RPC::decodeCTID("C003FFFFFFFFFFF")); + BEAST_EXPECT(!rpc::decodeCTID("C003FFFFFFFFFFF")); // Test case 9: ctid too large to be a valid CTID value - BEAST_EXPECT(!RPC::decodeCTID("CFFFFFFFFFFFFFFFF")); + BEAST_EXPECT(!rpc::decodeCTID("CFFFFFFFFFFFFFFFF")); // Test case 10: ctid doesn't start with a C nibble - BEAST_EXPECT(!RPC::decodeCTID("FFFFFFFFFFFFFFFF")); + BEAST_EXPECT(!rpc::decodeCTID("FFFFFFFFFFFFFFFF")); // Test case 11: Valid input values BEAST_EXPECT( - (RPC::decodeCTID(0xCFFF'FFFF'FFFF'FFFFULL) == + (rpc::decodeCTID(0xCFFF'FFFF'FFFF'FFFFULL) == std::optional>( std::make_tuple(0x0FFF'FFFFUL, 0xFFFFU, 0xFFFFU)))); BEAST_EXPECT( - (RPC::decodeCTID(0xC000'0000'0000'0000ULL) == + (rpc::decodeCTID(0xC000'0000'0000'0000ULL) == std::optional>(std::make_tuple(0, 0, 0)))); BEAST_EXPECT( - (RPC::decodeCTID(0xC000'0001'0002'0003ULL) == + (rpc::decodeCTID(0xC000'0001'0002'0003ULL) == std::optional>(std::make_tuple(1U, 2U, 3U)))); BEAST_EXPECT( - (RPC::decodeCTID(0xC0CA'2AA7'326F'C045ULL) == + (rpc::decodeCTID(0xC0CA'2AA7'326F'C045ULL) == std::optional>( std::make_tuple(1324'9191UL, 12911U, 49221U)))); // Test case 12: ctid not exactly 16 nibbles - BEAST_EXPECT(!RPC::decodeCTID(0xC003'FFFF'FFFF'FFF)); + BEAST_EXPECT(!rpc::decodeCTID(0xC003'FFFF'FFFF'FFF)); // Test case 13: ctid too large to be a valid CTID value // this test case is not possible in c++ because it would overflow the // type, left in for completeness - // BEAST_EXPECT(!RPC::decodeCTID(0xCFFFFFFFFFFFFFFFFULL)); + // BEAST_EXPECT(!rpc::decodeCTID(0xCFFFFFFFFFFFFFFFFULL)); // Test case 14: ctid doesn't start with a C nibble - BEAST_EXPECT(!RPC::decodeCTID(0xFFFF'FFFF'FFFF'FFFFULL)); + BEAST_EXPECT(!rpc::decodeCTID(0xFFFF'FFFF'FFFF'FFFFULL)); } void @@ -619,7 +619,7 @@ class Transaction_test : public beast::unit_test::Suite env(pay(alice, bob, XRP(10))); env.close(); - auto const ctid = RPC::encodeCTID(startLegSeq, 0, netID); + auto const ctid = rpc::encodeCTID(startLegSeq, 0, netID); if (netID > 0xFFFF) { // Concise transaction IDs do not support a network ID > 0xFFFF. @@ -650,7 +650,7 @@ class Transaction_test : public beast::unit_test::Suite env.close(); // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - std::string const ctid = *RPC::encodeCTID(startLegSeq, 0, netID); + std::string const ctid = *rpc::encodeCTID(startLegSeq, 0, netID); auto isUpper = [](char c) { return std::isupper(c) != 0; }; // Verify that there are at least two upper case letters in ctid and @@ -705,7 +705,7 @@ class Transaction_test : public beast::unit_test::Suite BEAST_EXPECT(jrr.isMember(jss::ctid) == (netID <= 0xFFFF)); if (jrr.isMember(jss::ctid)) { - auto const ctid = RPC::encodeCTID(ledgerSeq, 0, netID); + auto const ctid = rpc::encodeCTID(ledgerSeq, 0, netID); BEAST_EXPECT( jrr[jss::ctid] == *ctid); // NOLINT(bugprone-unchecked-optional-access) } @@ -725,7 +725,7 @@ class Transaction_test : public beast::unit_test::Suite env.close(); // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - auto const ctid = *RPC::encodeCTID(startLegSeq, 0, netID + 1); + auto const ctid = *rpc::encodeCTID(startLegSeq, 0, netID + 1); json::Value jsonTx; jsonTx[jss::binary] = false; jsonTx[jss::ctid] = ctid; diff --git a/src/test/rpc/Version_test.cpp b/src/test/rpc/Version_test.cpp index 71830c2219..b5c1abc160 100644 --- a/src/test/rpc/Version_test.cpp +++ b/src/test/rpc/Version_test.cpp @@ -32,7 +32,7 @@ class Version_test : public beast::unit_test::Suite auto jrr = env.rpc( "json", "version", - "{\"api_version\": " + std::to_string(RPC::kApiMaximumSupportedVersion) + + "{\"api_version\": " + std::to_string(rpc::kApiMaximumSupportedVersion) + "}")[jss::result]; BEAST_EXPECT(isCorrectReply(jrr)); @@ -62,7 +62,7 @@ class Version_test : public beast::unit_test::Suite auto re = env.rpc( "json", "version", - "{\"api_version\": " + std::to_string(RPC::kApiMinimumSupportedVersion - 1) + "}"); + "{\"api_version\": " + std::to_string(rpc::kApiMinimumSupportedVersion - 1) + "}"); BEAST_EXPECT(badVersion(re)); BEAST_EXPECT(env.app().config().betaRpcApi); @@ -71,7 +71,7 @@ class Version_test : public beast::unit_test::Suite "version", "{\"api_version\": " + std::to_string( - std::max(RPC::kApiMaximumSupportedVersion.value, RPC::kApiBetaVersion.value) + + std::max(rpc::kApiMaximumSupportedVersion.value, rpc::kApiBetaVersion.value) + 1) + "}"); BEAST_EXPECT(badVersion(re)); @@ -86,38 +86,38 @@ class Version_test : public beast::unit_test::Suite testcase("test getAPIVersionNumber function"); unsigned int const versionIfUnspecified = - RPC::kApiVersionIfUnspecified < RPC::kApiMinimumSupportedVersion - ? RPC::kApiInvalidVersion - : RPC::kApiVersionIfUnspecified; + rpc::kApiVersionIfUnspecified < rpc::kApiMinimumSupportedVersion + ? rpc::kApiInvalidVersion + : rpc::kApiVersionIfUnspecified; json::Value const jArray = json::Value(json::ValueType::Array); json::Value const jNull = json::Value(json::ValueType::Null); - BEAST_EXPECT(RPC::getAPIVersionNumber(jArray, false) == versionIfUnspecified); - BEAST_EXPECT(RPC::getAPIVersionNumber(jNull, false) == versionIfUnspecified); + BEAST_EXPECT(rpc::getAPIVersionNumber(jArray, false) == versionIfUnspecified); + BEAST_EXPECT(rpc::getAPIVersionNumber(jNull, false) == versionIfUnspecified); json::Value jObject = json::Value(json::ValueType::Object); - BEAST_EXPECT(RPC::getAPIVersionNumber(jObject, false) == versionIfUnspecified); - jObject[jss::api_version] = RPC::kApiVersionIfUnspecified.value; - BEAST_EXPECT(RPC::getAPIVersionNumber(jObject, false) == versionIfUnspecified); + BEAST_EXPECT(rpc::getAPIVersionNumber(jObject, false) == versionIfUnspecified); + jObject[jss::api_version] = rpc::kApiVersionIfUnspecified.value; + BEAST_EXPECT(rpc::getAPIVersionNumber(jObject, false) == versionIfUnspecified); - jObject[jss::api_version] = RPC::kApiMinimumSupportedVersion.value; - BEAST_EXPECT(RPC::getAPIVersionNumber(jObject, false) == RPC::kApiMinimumSupportedVersion); - jObject[jss::api_version] = RPC::kApiMaximumSupportedVersion.value; - BEAST_EXPECT(RPC::getAPIVersionNumber(jObject, false) == RPC::kApiMaximumSupportedVersion); + jObject[jss::api_version] = rpc::kApiMinimumSupportedVersion.value; + BEAST_EXPECT(rpc::getAPIVersionNumber(jObject, false) == rpc::kApiMinimumSupportedVersion); + jObject[jss::api_version] = rpc::kApiMaximumSupportedVersion.value; + BEAST_EXPECT(rpc::getAPIVersionNumber(jObject, false) == rpc::kApiMaximumSupportedVersion); - jObject[jss::api_version] = RPC::kApiMinimumSupportedVersion - 1; - BEAST_EXPECT(RPC::getAPIVersionNumber(jObject, false) == RPC::kApiInvalidVersion); - jObject[jss::api_version] = RPC::kApiMaximumSupportedVersion + 1; - BEAST_EXPECT(RPC::getAPIVersionNumber(jObject, false) == RPC::kApiInvalidVersion); - jObject[jss::api_version] = RPC::kApiBetaVersion.value; - BEAST_EXPECT(RPC::getAPIVersionNumber(jObject, true) == RPC::kApiBetaVersion); - jObject[jss::api_version] = RPC::kApiBetaVersion + 1; - BEAST_EXPECT(RPC::getAPIVersionNumber(jObject, true) == RPC::kApiInvalidVersion); + jObject[jss::api_version] = rpc::kApiMinimumSupportedVersion - 1; + BEAST_EXPECT(rpc::getAPIVersionNumber(jObject, false) == rpc::kApiInvalidVersion); + jObject[jss::api_version] = rpc::kApiMaximumSupportedVersion + 1; + BEAST_EXPECT(rpc::getAPIVersionNumber(jObject, false) == rpc::kApiInvalidVersion); + jObject[jss::api_version] = rpc::kApiBetaVersion.value; + BEAST_EXPECT(rpc::getAPIVersionNumber(jObject, true) == rpc::kApiBetaVersion); + jObject[jss::api_version] = rpc::kApiBetaVersion + 1; + BEAST_EXPECT(rpc::getAPIVersionNumber(jObject, true) == rpc::kApiInvalidVersion); - jObject[jss::api_version] = RPC::kApiInvalidVersion.value; - BEAST_EXPECT(RPC::getAPIVersionNumber(jObject, false) == RPC::kApiInvalidVersion); + jObject[jss::api_version] = rpc::kApiInvalidVersion.value; + BEAST_EXPECT(rpc::getAPIVersionNumber(jObject, false) == rpc::kApiInvalidVersion); jObject[jss::api_version] = "a"; - BEAST_EXPECT(RPC::getAPIVersionNumber(jObject, false) == RPC::kApiInvalidVersion); + BEAST_EXPECT(rpc::getAPIVersionNumber(jObject, false) == rpc::kApiInvalidVersion); } void @@ -141,7 +141,7 @@ class Version_test : public beast::unit_test::Suite "\"method\": \"version\", " "\"params\": { " "\"api_version\": " + - std::to_string(RPC::kApiMaximumSupportedVersion) + "}}"; + std::to_string(rpc::kApiMaximumSupportedVersion) + "}}"; auto re = env.rpc("json2", '[' + withoutApiVerion + ", " + withApiVerion + ']'); if (!BEAST_EXPECT(re.isArray())) @@ -176,7 +176,7 @@ class Version_test : public beast::unit_test::Suite "\"params\": { " "\"api_version\": " + std::to_string( - std::max(RPC::kApiMaximumSupportedVersion.value, RPC::kApiBetaVersion.value) + 1) + + std::max(rpc::kApiMaximumSupportedVersion.value, rpc::kApiBetaVersion.value) + 1) + "}}"; auto re = env.rpc("json2", '[' + withoutApiVerion + ", " + withWrongApiVerion + ']'); @@ -226,15 +226,15 @@ class Version_test : public beast::unit_test::Suite auto jrr = env.rpc( "json", "version", - "{\"api_version\": " + std::to_string(RPC::kApiBetaVersion) + "}")[jss::result]; + "{\"api_version\": " + std::to_string(rpc::kApiBetaVersion) + "}")[jss::result]; if (!BEAST_EXPECT(jrr.isMember(jss::version))) return; if (!BEAST_EXPECT(jrr[jss::version].isMember(jss::first)) && jrr[jss::version].isMember(jss::last)) return; - BEAST_EXPECT(jrr[jss::version][jss::first] == RPC::kApiMinimumSupportedVersion.value); - BEAST_EXPECT(jrr[jss::version][jss::last] == RPC::kApiBetaVersion.value); + BEAST_EXPECT(jrr[jss::version][jss::first] == rpc::kApiMinimumSupportedVersion.value); + BEAST_EXPECT(jrr[jss::version][jss::last] == rpc::kApiBetaVersion.value); } public: diff --git a/src/tests/libxrpl/helpers/TestServiceRegistry.h b/src/tests/libxrpl/helpers/TestServiceRegistry.h index f7b09bccd1..e763c8bde4 100644 --- a/src/tests/libxrpl/helpers/TestServiceRegistry.h +++ b/src/tests/libxrpl/helpers/TestServiceRegistry.h @@ -213,7 +213,7 @@ public: throw std::logic_error("TestServiceRegistry::peerReservations() not implemented"); } - Resource::Manager& + resource::Manager& getResourceManager() override { throw std::logic_error("TestServiceRegistry::getResourceManager() not implemented"); diff --git a/src/tests/libxrpl/peerfinder/Livecache.cpp b/src/tests/libxrpl/peerfinder/Livecache.cpp index 464ec0e5da..298b09e04e 100644 --- a/src/tests/libxrpl/peerfinder/Livecache.cpp +++ b/src/tests/libxrpl/peerfinder/Livecache.cpp @@ -29,7 +29,7 @@ #include #include -namespace xrpl::PeerFinder { +namespace xrpl::peer_finder { namespace { class LivecacheTest : public ::testing::Test @@ -41,22 +41,22 @@ protected: return beast::Journal{TestSink::instance()}; } - static beast::IP::Endpoint + static beast::ip::Endpoint endpoint(std::uint16_t index, bool v4 = true) { auto const port = static_cast(10000 + index); if (v4) { - auto bytes = beast::IP::AddressV4::bytes_type{ + auto bytes = beast::ip::AddressV4::bytes_type{ {54, static_cast((index / 256) % 256), static_cast(index % 256), 1}}; - return beast::IP::Endpoint{beast::IP::Address{beast::IP::AddressV4{bytes}}, port}; + return beast::ip::Endpoint{beast::ip::Address{beast::ip::AddressV4{bytes}}, port}; } - auto bytes = beast::IP::AddressV6::bytes_type{ + auto bytes = beast::ip::AddressV6::bytes_type{ {0x20, 0x01, 0x0d, @@ -73,11 +73,11 @@ protected: static_cast((index / 256) % 256), static_cast(index % 256), 1}}; - return beast::IP::Endpoint{beast::IP::Address{beast::IP::AddressV6{bytes}}, port}; + return beast::ip::Endpoint{beast::ip::Address{beast::ip::AddressV6{bytes}}, port}; } void - addEndpoint(beast::IP::Endpoint const& ep, std::uint32_t hops = 0) + addEndpoint(beast::ip::Endpoint const& ep, std::uint32_t hops = 0) { cache_.insert(Endpoint{ep, hops}); } @@ -161,7 +161,7 @@ TEST_F(LivecacheTest, hop_iterators_support_const_reverse_and_move_back) TEST_F(LivecacheTest, on_write_reports_entries_and_expiration) { cache_.insert(Endpoint{endpoint(1), 1}); - cache_.insert(Endpoint{endpoint(2), Tuning::kMaxHops + 1}); + cache_.insert(Endpoint{endpoint(2), tuning::kMaxHops + 1}); JsonPropertyStream stream; { @@ -190,7 +190,7 @@ TEST_F(LivecacheTest, expire_removes_entries_after_ttl) cache_.expire(); EXPECT_EQ(cache_.size(), 1u); - clock_.advance(Tuning::kLiveCacheSecondsToLive - 1s); + clock_.advance(tuning::kLiveCacheSecondsToLive - 1s); cache_.expire(); EXPECT_EQ(cache_.size(), 1u); @@ -206,7 +206,7 @@ TEST_F(LivecacheTest, expire_removes_multiple_entries_after_ttl) cache_.insert(Endpoint{endpoint(1), 1}); cache_.insert(Endpoint{endpoint(2), 2}); - clock_.advance(Tuning::kLiveCacheSecondsToLive); + clock_.advance(tuning::kLiveCacheSecondsToLive); cache_.expire(); EXPECT_TRUE(cache_.empty()); } @@ -240,11 +240,11 @@ TEST_F(LivecacheTest, shuffle_preserves_bucket_contents) { for (auto i = 0; i < 100; ++i) { - addEndpoint(endpoint(static_cast(i)), xrpl::randInt(Tuning::kMaxHops + 1)); + addEndpoint(endpoint(static_cast(i)), xrpl::randInt(tuning::kMaxHops + 1)); } using AtHop = std::vector; - using AllHops = std::array; + using AllHops = std::array; auto const compareEndpoint = [](Endpoint const& lhs, Endpoint const& rhs) { return rhs.hops < lhs.hops || (rhs.hops == lhs.hops && rhs.address < lhs.address); @@ -291,4 +291,4 @@ TEST_F(LivecacheTest, shuffle_preserves_bucket_contents) EXPECT_FALSE(allBucketsKeptOriginalOrder); } -} // namespace xrpl::PeerFinder +} // namespace xrpl::peer_finder diff --git a/src/tests/libxrpl/peerfinder/PeerFinder.cpp b/src/tests/libxrpl/peerfinder/PeerFinder.cpp index 31fa59d1ce..3a52bbb5aa 100644 --- a/src/tests/libxrpl/peerfinder/PeerFinder.cpp +++ b/src/tests/libxrpl/peerfinder/PeerFinder.cpp @@ -38,7 +38,7 @@ #include #include -namespace xrpl::PeerFinder { +namespace xrpl::peer_finder { namespace { using ::testing::_; @@ -51,10 +51,10 @@ journal() return beast::Journal{TestSink::instance()}; } -beast::IP::Endpoint +beast::ip::Endpoint endpoint(std::string const& value) { - return beast::IP::Endpoint::fromString(value); + return beast::ip::Endpoint::fromString(value); } class MockStore : public Store @@ -86,7 +86,7 @@ public: }; Store::Entry -storeEntry(beast::IP::Endpoint const& endpoint, int valence) +storeEntry(beast::ip::Endpoint const& endpoint, int valence) { Store::Entry entry; entry.endpoint = endpoint; @@ -106,15 +106,15 @@ class MockChecker public: MOCK_METHOD(void, stop, ()); MOCK_METHOD(void, wait, ()); - MOCK_METHOD(void, recordAsyncConnect, (beast::IP::Endpoint const& ep)); + MOCK_METHOD(void, recordAsyncConnect, (beast::ip::Endpoint const& ep)); boost::system::error_code nextError; bool completeAsync = true; - std::vector asyncConnects; + std::vector asyncConnects; template void - asyncConnect(beast::IP::Endpoint const& ep, Handler&& handler) + asyncConnect(beast::ip::Endpoint const& ep, Handler&& handler) { asyncConnects.push_back(ep); recordAsyncConnect(ep); @@ -204,7 +204,7 @@ protected: }; int -savedValence(std::vector const& entries, beast::IP::Endpoint const& endpoint) +savedValence(std::vector const& entries, beast::ip::Endpoint const& endpoint) { for (auto const& entry : entries) { @@ -458,7 +458,7 @@ TEST(PeerFinderCounts, tracks_slot_states_and_capacity) EXPECT_EQ(counts.outboundSlotsFree(), 1); EXPECT_EQ(counts.totalActive(), 0); EXPECT_FALSE(counts.isConnectedToNetwork()); - EXPECT_EQ(counts.attemptsNeeded(), Tuning::kMaxConnectAttempts); + EXPECT_EQ(counts.attemptsNeeded(), tuning::kMaxConnectAttempts); EXPECT_EQ(counts.stateString(), "0/1 out, 0/1 in, 0 connecting, 0 closing"); SlotImp inbound(endpoint("65.0.0.1:10001"), endpoint("65.0.0.2:10002"), false, clock); @@ -483,7 +483,7 @@ TEST(PeerFinderCounts, tracks_slot_states_and_capacity) counts.add(outbound); EXPECT_EQ(counts.attempts(), 1); EXPECT_EQ(counts.connectCount(), 1); - EXPECT_EQ(counts.attemptsNeeded(), Tuning::kMaxConnectAttempts - 1); + EXPECT_EQ(counts.attemptsNeeded(), tuning::kMaxConnectAttempts - 1); counts.remove(outbound); outbound.state(Slot::State::Connected); @@ -535,7 +535,7 @@ TEST(PeerFinderCounts, tracks_slot_states_and_capacity) Counts saturatedAttempts; saturatedAttempts.onConfig(config); std::vector> attempts; - for (int i = 0; i < Tuning::kMaxConnectAttempts; ++i) + for (int i = 0; i < tuning::kMaxConnectAttempts; ++i) { attempts.push_back( std::make_unique( @@ -544,7 +544,7 @@ TEST(PeerFinderCounts, tracks_slot_states_and_capacity) clock)); saturatedAttempts.add(*attempts.back()); } - EXPECT_EQ(saturatedAttempts.attempts(), Tuning::kMaxConnectAttempts); + EXPECT_EQ(saturatedAttempts.attempts(), tuning::kMaxConnectAttempts); EXPECT_EQ(saturatedAttempts.attemptsNeeded(), 0u); Config disconnected; @@ -563,7 +563,7 @@ TEST(PeerFinderHandouts, filters_redirect_slot_and_connect_targets) EXPECT_EQ(redirects.slot(), slot); EXPECT_TRUE(redirects.list().empty()); EXPECT_FALSE(redirects.full()); - EXPECT_FALSE(redirects.tryInsert(Endpoint{endpoint("65.0.0.3:10003"), Tuning::kMaxHops + 1})); + EXPECT_FALSE(redirects.tryInsert(Endpoint{endpoint("65.0.0.3:10003"), tuning::kMaxHops + 1})); EXPECT_FALSE(redirects.tryInsert(Endpoint{endpoint("65.0.0.3:10003"), 0})); EXPECT_FALSE(redirects.tryInsert(Endpoint{remote.atPort(12000), 1})); EXPECT_TRUE(redirects.tryInsert(Endpoint{endpoint("65.0.0.3:10003"), 1})); @@ -574,7 +574,7 @@ TEST(PeerFinderHandouts, filters_redirect_slot_and_connect_targets) EXPECT_EQ(slotHandouts.slot(), slot); EXPECT_FALSE(slotHandouts.full()); EXPECT_FALSE( - slotHandouts.tryInsert(Endpoint{endpoint("65.0.0.4:10004"), Tuning::kMaxHops + 1})); + slotHandouts.tryInsert(Endpoint{endpoint("65.0.0.4:10004"), tuning::kMaxHops + 1})); EXPECT_FALSE(slotHandouts.tryInsert(Endpoint{remote.atPort(12001), 1})); auto const recent = endpoint("65.0.0.5:10005"); @@ -620,7 +620,7 @@ TEST(PeerFinderHandouts, distributes_livecache_entries) EXPECT_FALSE(targets.front().list().empty()); EXPECT_FALSE(targets.back().list().empty()); - for (std::uint32_t i = 0; i < Tuning::kNumberOfEndpoints; ++i) + for (std::uint32_t i = 0; i < tuning::kNumberOfEndpoints; ++i) targets.front().insert(Endpoint{endpoint("65.1.0." + std::to_string(i + 1) + ":12000"), 1}); handout(targets.begin(), targets.begin() + 1, cache.hops.begin(), cache.hops.end()); @@ -633,7 +633,7 @@ TEST_F(PeerFinderTest, preprocess_filters_invalid_duplicate_and_extra_self_endpo auto const remote = endpoint("65.0.0.2:10002"); auto const slot = std::make_shared(local, remote, false, clock_); Endpoints endpoints{ - Endpoint{endpoint("65.0.0.3:10003"), Tuning::kMaxHops + 1}, + Endpoint{endpoint("65.0.0.3:10003"), tuning::kMaxHops + 1}, Endpoint{endpoint("0.0.0.0:2459"), 0}, Endpoint{endpoint("0.0.0.0:2460"), 0}, Endpoint{endpoint("10.0.0.1:10004"), 1}, @@ -677,7 +677,7 @@ TEST_F(PeerFinderTest, on_endpoints_checks_neighbor_before_caching_it) EXPECT_TRUE(slot->canAccept); EXPECT_TRUE(logic_.livecache.empty()); - clock_.advance(Tuning::kSecondsPerMessage); + clock_.advance(tuning::kSecondsPerMessage); logic_.onEndpoints(slot, advertised); EXPECT_EQ(logic_.livecache.size(), 1u); EXPECT_EQ(logic_.bootcache.size(), 1u); @@ -711,7 +711,7 @@ TEST_F(PeerFinderTest, on_endpoints_skips_failed_neighbor_connectivity_checks) EXPECT_TRUE(slot->checked); EXPECT_FALSE(slot->canAccept); - clock_.advance(Tuning::kSecondsPerMessage); + clock_.advance(tuning::kSecondsPerMessage); logic_.onEndpoints(slot, advertised); EXPECT_TRUE(logic_.livecache.empty()); @@ -740,7 +740,7 @@ TEST_F(PeerFinderTest, on_endpoints_waits_for_pending_connectivity_check) logic_.onEndpoints(slot, advertised); EXPECT_TRUE(slot->connectivityCheckInProgress); - clock_.advance(Tuning::kSecondsPerMessage); + clock_.advance(tuning::kSecondsPerMessage); logic_.onEndpoints(slot, advertised); EXPECT_EQ(checker_.asyncConnects.size(), 1u); EXPECT_TRUE(logic_.livecache.empty()); @@ -950,7 +950,7 @@ TEST(PeerFinderBootcache, periodic_activity_saves_after_cooldown) cache.periodicActivity(); EXPECT_TRUE(store.saves.empty()); - clock.advance(Tuning::kBootcacheCooldownTime + 1s); + clock.advance(tuning::kBootcacheCooldownTime + 1s); cache.periodicActivity(); ASSERT_EQ(store.saves.size(), 1u); @@ -967,23 +967,23 @@ TEST(PeerFinderBootcache, prunes_when_cache_exceeds_limit) TestStopwatch clock; Bootcache cache(store, clock, journal()); - for (std::uint16_t i = 0; i <= Tuning::kBootcacheSize; ++i) + for (std::uint16_t i = 0; i <= tuning::kBootcacheSize; ++i) { EXPECT_TRUE(cache.insert(endpoint( "65.0." + std::to_string((i / 256) % 256) + "." + std::to_string(i % 256) + ":" + std::to_string(10000 + i)))); } - EXPECT_LE(cache.size(), Tuning::kBootcacheSize); + EXPECT_LE(cache.size(), tuning::kBootcacheSize); } TEST(PeerFinderEndpoint, clamps_hops_to_overflow_bucket) { auto const address = endpoint("65.0.0.1:10001"); - Endpoint const ep(address, Tuning::kMaxHops + 10); + Endpoint const ep(address, tuning::kMaxHops + 10); EXPECT_EQ(ep.address, address); - EXPECT_EQ(ep.hops, Tuning::kMaxHops + 1); + EXPECT_EQ(ep.hops, tuning::kMaxHops + 1); } TEST(PeerFinderSlotImp, tracks_state_and_recent_endpoints) @@ -1001,7 +1001,7 @@ TEST(PeerFinderSlotImp, tracks_state_and_recent_endpoints) EXPECT_FALSE(inbound.reserved()); EXPECT_EQ(inbound.state(), State::Accept); EXPECT_EQ(inbound.remoteEndpoint(), remote); - EXPECT_EQ(inbound.localEndpoint(), std::optional{local}); + EXPECT_EQ(inbound.localEndpoint(), std::optional{local}); EXPECT_FALSE(inbound.publicKey()); EXPECT_FALSE(inbound.listeningPort()); EXPECT_FALSE(inbound.checked); @@ -1018,7 +1018,7 @@ TEST(PeerFinderSlotImp, tracks_state_and_recent_endpoints) inbound.reserved(true); inbound.setListeningPort(2459); - EXPECT_EQ(inbound.localEndpoint(), std::optional{newLocal}); + EXPECT_EQ(inbound.localEndpoint(), std::optional{newLocal}); EXPECT_EQ(inbound.remoteEndpoint(), newRemote); EXPECT_EQ(inbound.publicKey(), std::optional{publicKey}); EXPECT_TRUE(inbound.reserved()); @@ -1055,7 +1055,7 @@ TEST(PeerFinderSlotImp, tracks_state_and_recent_endpoints) EXPECT_TRUE(outbound.recent.filter(recent, 1)); EXPECT_FALSE(outbound.recent.filter(recent, 0)); - clock.advance(Tuning::kLiveCacheSecondsToLive + 1s); + clock.advance(tuning::kLiveCacheSecondsToLive + 1s); outbound.expire(); EXPECT_FALSE(outbound.recent.filter(recent, 1)); } @@ -1112,7 +1112,7 @@ TEST(PeerFinderConfig, calculates_outbound_peers_and_clamps_ip_limits) { Config config; config.maxPeers = 1; - EXPECT_EQ(config.calcOutPeers(), Tuning::kMinOutCount); + EXPECT_EQ(config.calcOutPeers(), tuning::kMinOutCount); config.maxPeers = 100; EXPECT_EQ(config.calcOutPeers(), 15u); @@ -1267,4 +1267,4 @@ TEST(PeerFinderConfig, rejects_incomplete_or_out_of_range_peer_limits) } } // namespace -} // namespace xrpl::PeerFinder +} // namespace xrpl::peer_finder diff --git a/src/tests/libxrpl/protocol/ApiVersion.cpp b/src/tests/libxrpl/protocol/ApiVersion.cpp index 8af7787102..5bb6a158cf 100644 --- a/src/tests/libxrpl/protocol/ApiVersion.cpp +++ b/src/tests/libxrpl/protocol/ApiVersion.cpp @@ -6,21 +6,21 @@ using namespace xrpl; TEST(ApiVersion, invariants) { - static_assert(RPC::kApiMinimumSupportedVersion <= RPC::kApiMaximumSupportedVersion); - static_assert(RPC::kApiMinimumSupportedVersion <= RPC::kApiMaximumValidVersion); - static_assert(RPC::kApiMaximumSupportedVersion <= RPC::kApiMaximumValidVersion); - static_assert(RPC::kApiBetaVersion <= RPC::kApiMaximumValidVersion); + static_assert(rpc::kApiMinimumSupportedVersion <= rpc::kApiMaximumSupportedVersion); + static_assert(rpc::kApiMinimumSupportedVersion <= rpc::kApiMaximumValidVersion); + static_assert(rpc::kApiMaximumSupportedVersion <= rpc::kApiMaximumValidVersion); + static_assert(rpc::kApiBetaVersion <= rpc::kApiMaximumValidVersion); } // Update when we change versions TEST(ApiVersion, versions) { - static_assert(RPC::kApiMinimumSupportedVersion >= 1); - static_assert(RPC::kApiMinimumSupportedVersion < 2); - static_assert(RPC::kApiMaximumSupportedVersion >= 2); - static_assert(RPC::kApiMaximumSupportedVersion < 3); - static_assert(RPC::kApiMaximumValidVersion >= 3); - static_assert(RPC::kApiMaximumValidVersion < 4); - static_assert(RPC::kApiBetaVersion >= 3); - static_assert(RPC::kApiBetaVersion < 4); + static_assert(rpc::kApiMinimumSupportedVersion >= 1); + static_assert(rpc::kApiMinimumSupportedVersion < 2); + static_assert(rpc::kApiMaximumSupportedVersion >= 2); + static_assert(rpc::kApiMaximumSupportedVersion < 3); + static_assert(rpc::kApiMaximumValidVersion >= 3); + static_assert(rpc::kApiMaximumValidVersion < 4); + static_assert(rpc::kApiBetaVersion >= 3); + static_assert(rpc::kApiBetaVersion < 4); } diff --git a/src/tests/libxrpl/resource/Logic.cpp b/src/tests/libxrpl/resource/Logic.cpp index a3362b4540..b38ca2e051 100644 --- a/src/tests/libxrpl/resource/Logic.cpp +++ b/src/tests/libxrpl/resource/Logic.cpp @@ -24,7 +24,7 @@ #include #include -namespace xrpl::Resource { +namespace xrpl::resource { class ResourceManagerTest : public ::testing::Test { @@ -68,13 +68,13 @@ protected: { Gossip::Item item; item.balance = 100 + randInt(499); - beast::IP::AddressV4::bytes_type const d = {{ + beast::ip::AddressV4::bytes_type const d = {{ 192, 0, 2, static_cast(v + i), }}; - item.address = beast::IP::Endpoint{beast::IP::AddressV4{d}}; + item.address = beast::ip::Endpoint{beast::ip::AddressV4{d}}; gossip.items.push_back(std::move(item)); } return gossip; @@ -86,7 +86,7 @@ TEST_F(ResourceManagerTest, limited_warn_drop) TestLogic logic{j_}; Charge const fee{kDropThreshold + 1}; - beast::IP::Endpoint const addr{beast::IP::Endpoint::fromString("192.0.2.2")}; + beast::ip::Endpoint const addr{beast::ip::Endpoint::fromString("192.0.2.2")}; { Consumer c{logic.newInboundEndpoint(addr)}; @@ -158,7 +158,7 @@ TEST_F(ResourceManagerTest, unlimited_warn_drop) TestLogic logic{j_}; Charge const fee{kDropThreshold + 1}; - beast::IP::Endpoint const addr{beast::IP::Endpoint::fromString("192.0.2.2")}; + beast::ip::Endpoint const addr{beast::ip::Endpoint::fromString("192.0.2.2")}; Consumer c{logic.newUnlimitedEndpoint(addr)}; // Create load until we get a warning @@ -185,7 +185,7 @@ TEST_F(ResourceManagerTest, charges) TestLogic logic{j_}; { - beast::IP::Endpoint const address{beast::IP::Endpoint::fromString("192.0.2.1")}; + beast::ip::Endpoint const address{beast::ip::Endpoint::fromString("192.0.2.1")}; Consumer c{logic.newInboundEndpoint(address)}; Charge const fee{1000}; JLOG(j_.info()) << "Charging " << c.toString() << " " << fee << " per second"; @@ -199,7 +199,7 @@ TEST_F(ResourceManagerTest, charges) } { - beast::IP::Endpoint const address{beast::IP::Endpoint::fromString("192.0.2.2")}; + beast::ip::Endpoint const address{beast::ip::Endpoint::fromString("192.0.2.2")}; Consumer c{logic.newInboundEndpoint(address)}; Charge const fee{1000}; JLOG(j_.info()) << "Charging " << c.toString() << " " << fee << " per second"; @@ -230,16 +230,16 @@ TEST_F(ResourceManagerTest, import) Gossip g; Gossip::Item item; item.balance = 100; - beast::IP::AddressV4::bytes_type const d = {{ + beast::ip::AddressV4::bytes_type const d = {{ 192, 0, 2, 1, }}; - item.address = beast::IP::Endpoint{beast::IP::AddressV4{d}}; + item.address = beast::ip::Endpoint{beast::ip::AddressV4{d}}; g.items.push_back(std::move(item)); logic.importConsumers("g", g); } -} // namespace xrpl::Resource +} // namespace xrpl::resource diff --git a/src/xrpld/app/consensus/RCLConsensus.cpp b/src/xrpld/app/consensus/RCLConsensus.cpp index 20421ab916..28b910c8e5 100644 --- a/src/xrpld/app/consensus/RCLConsensus.cpp +++ b/src/xrpld/app/consensus/RCLConsensus.cpp @@ -847,7 +847,7 @@ RCLConsensus::Adaptor::validate(RCLCxLedger const& ledger, RCLTxSet const& txns, // Report our server version every flag ledger: if (ledger.ledger->isVotingLedger()) - v.setFieldU64(sfServerVersion, BuildInfo::getEncodedVersion()); + v.setFieldU64(sfServerVersion, build_info::getEncodedVersion()); // Report our load { diff --git a/src/xrpld/app/ledger/LedgerReplayer.h b/src/xrpld/app/ledger/LedgerReplayer.h index 6feb187df6..b2806ee813 100644 --- a/src/xrpld/app/ledger/LedgerReplayer.h +++ b/src/xrpld/app/ledger/LedgerReplayer.h @@ -24,7 +24,7 @@ namespace test { class LedgerReplayClient; } // namespace test -namespace LedgerReplayParameters { +namespace ledger_replay_parameters { // timeout value for LedgerReplayTask constexpr auto kTaskTimeout = std::chrono::milliseconds{500}; @@ -53,7 +53,7 @@ constexpr std::uint32_t kMaxTaskSize = 256; // to limit the number of LedgerReplay related jobs in JobQueue constexpr std::uint32_t kMaxQueuedTasks = 100; -} // namespace LedgerReplayParameters +} // namespace ledger_replay_parameters /** * Manages the lifetime of ledger replay tasks. diff --git a/src/xrpld/app/ledger/LedgerToJson.h b/src/xrpld/app/ledger/LedgerToJson.h index 1eac4d68f1..e1172e897a 100644 --- a/src/xrpld/app/ledger/LedgerToJson.h +++ b/src/xrpld/app/ledger/LedgerToJson.h @@ -18,7 +18,7 @@ struct LedgerFill { LedgerFill( ReadView const& l, - RPC::Context const* ctx, + rpc::Context const* ctx, int o = 0, std::vector q = {}) : ledger(l), options(o), txQueue(std::move(q)), context(ctx) @@ -40,7 +40,7 @@ struct LedgerFill ReadView const& ledger; int options; std::vector txQueue; - RPC::Context const* context; + rpc::Context const* context; std::optional closeTime; }; diff --git a/src/xrpld/app/ledger/detail/InboundLedger.cpp b/src/xrpld/app/ledger/detail/InboundLedger.cpp index b3dafcf5e6..246c7d567b 100644 --- a/src/xrpld/app/ledger/detail/InboundLedger.cpp +++ b/src/xrpld/app/ledger/detail/InboundLedger.cpp @@ -879,7 +879,7 @@ InboundLedger::receiveNode( { JLOG(journal_.warn()) << "Got invalid node data for ledger " << hash_ << " from peer " << peer->id(); - peer->charge(Resource::kFeeInvalidData, "ledger_node.node_data invalid"); + peer->charge(resource::kFeeInvalidData, "ledger_node.node_data invalid"); san.incInvalid(); return; } @@ -889,7 +889,7 @@ InboundLedger::receiveNode( { JLOG(journal_.warn()) << "Got invalid node id for ledger " << hash_ << " from peer " << peer->id(); - peer->charge(Resource::kFeeInvalidData, "ledger_node.node_id invalid"); + peer->charge(resource::kFeeInvalidData, "ledger_node.node_id invalid"); san.incInvalid(); return; } @@ -903,7 +903,7 @@ InboundLedger::receiveNode( { JLOG(journal_.warn()) << "Got invalid node " << *nodeID << " for ledger " << hash_ << " from peer " << peer->id(); - peer->charge(Resource::kFeeInvalidData, "ledger_node invalid"); + peer->charge(resource::kFeeInvalidData, "ledger_node invalid"); return; } } @@ -1068,7 +1068,7 @@ InboundLedger::gotData( * Returns the number of useful nodes */ // VFALCO NOTE, it is not necessary to pass the entire Peer, -// we can get away with just a Resource::Consumer endpoint. +// we can get away with just a resource::Consumer endpoint. // // TODO Change peer to Consumer // @@ -1080,7 +1080,7 @@ InboundLedger::processData(std::shared_ptr peer, protocol::TMLedgerData co if (packet.nodes().empty()) { JLOG(journal_.warn()) << peer->id() << ": empty header data"; - peer->charge(Resource::kFeeMalformedRequest, "ledger_data empty header"); + peer->charge(resource::kFeeMalformedRequest, "ledger_data empty header"); return -1; } @@ -1095,7 +1095,7 @@ InboundLedger::processData(std::shared_ptr peer, protocol::TMLedgerData co if (!takeHeader(packet.nodes(0).nodedata())) { JLOG(journal_.warn()) << "Got invalid header data"; - peer->charge(Resource::kFeeMalformedRequest, "ledger_data invalid header"); + peer->charge(resource::kFeeMalformedRequest, "ledger_data invalid header"); return -1; } @@ -1109,7 +1109,7 @@ InboundLedger::processData(std::shared_ptr peer, protocol::TMLedgerData co << " from peer " << peer->id(); if (san.isInvalid()) { - peer->charge(Resource::kFeeInvalidData, "ledger_data invalid AS root"); + peer->charge(resource::kFeeInvalidData, "ledger_data invalid AS root"); return -1; } } @@ -1121,7 +1121,7 @@ InboundLedger::processData(std::shared_ptr peer, protocol::TMLedgerData co << " from peer " << peer->id(); if (san.isInvalid()) { - peer->charge(Resource::kFeeInvalidData, "ledger_data invalid TX root"); + peer->charge(resource::kFeeInvalidData, "ledger_data invalid TX root"); return -1; } } @@ -1131,7 +1131,7 @@ InboundLedger::processData(std::shared_ptr peer, protocol::TMLedgerData co JLOG(journal_.warn()) << "Included AS/TX root invalid for ledger " << hash_ << " from peer " << peer->id() << ": " << ex.what(); using namespace std::string_literals; - peer->charge(Resource::kFeeInvalidData, "ledger_data "s + ex.what()); + peer->charge(resource::kFeeInvalidData, "ledger_data "s + ex.what()); return -1; } @@ -1147,7 +1147,7 @@ InboundLedger::processData(std::shared_ptr peer, protocol::TMLedgerData co if (packet.nodes().empty()) { JLOG(journal_.info()) << peer->id() << ": response with no nodes"; - peer->charge(Resource::kFeeMalformedRequest, "ledger_data no nodes"); + peer->charge(resource::kFeeMalformedRequest, "ledger_data no nodes"); return -1; } diff --git a/src/xrpld/app/ledger/detail/InboundTransactions.cpp b/src/xrpld/app/ledger/detail/InboundTransactions.cpp index d735a97d28..62897fe617 100644 --- a/src/xrpld/app/ledger/detail/InboundTransactions.cpp +++ b/src/xrpld/app/ledger/detail/InboundTransactions.cpp @@ -138,7 +138,7 @@ public: if (ta == nullptr) { - peer->charge(Resource::kFeeUselessData, "ledger_data useless"); + peer->charge(resource::kFeeUselessData, "ledger_data useless"); return; } @@ -152,7 +152,7 @@ public: { JLOG(j_.warn()) << "Got invalid node data for TX set " << hash << " from peer " << peer->id(); - peer->charge(Resource::kFeeInvalidData, "ledger_node.node_data invalid"); + peer->charge(resource::kFeeInvalidData, "ledger_node.node_data invalid"); return; } @@ -161,7 +161,7 @@ public: { JLOG(j_.warn()) << "Got invalid node id for TX set " << hash << " from peer " << peer->id(); - peer->charge(Resource::kFeeInvalidData, "ledger_node.node_id invalid"); + peer->charge(resource::kFeeInvalidData, "ledger_node.node_id invalid"); return; } @@ -171,11 +171,11 @@ public: auto const san = ta->takeNodes(std::move(data), peer); if (san.isInvalid()) { - peer->charge(Resource::kFeeInvalidData, "ledger_data invalid"); + peer->charge(resource::kFeeInvalidData, "ledger_data invalid"); } else if (!san.isUseful()) { - peer->charge(Resource::kFeeUselessData, "ledger_data useless"); + peer->charge(resource::kFeeUselessData, "ledger_data useless"); } } diff --git a/src/xrpld/app/ledger/detail/LedgerDeltaAcquire.cpp b/src/xrpld/app/ledger/detail/LedgerDeltaAcquire.cpp index 7ac85b892e..344d5cb8fc 100644 --- a/src/xrpld/app/ledger/detail/LedgerDeltaAcquire.cpp +++ b/src/xrpld/app/ledger/detail/LedgerDeltaAcquire.cpp @@ -43,10 +43,10 @@ LedgerDeltaAcquire::LedgerDeltaAcquire( : TimeoutCounter( app, ledgerHash, - LedgerReplayParameters::kSubTaskTimeout, + ledger_replay_parameters::kSubTaskTimeout, {.jobType = JtReplayTask, .jobName = "LedReplDelta", - .jobLimit = LedgerReplayParameters::kMaxQueuedTasks}, + .jobLimit = ledger_replay_parameters::kMaxQueuedTasks}, app.getJournal("LedgerReplayDelta")) , inboundLedgers_(inboundLedgers) , ledgerSeq_(ledgerSeq) @@ -101,10 +101,10 @@ LedgerDeltaAcquire::trigger(std::size_t limit, ScopedLockType& sl) } else { - if (++noFeaturePeerCount_ >= LedgerReplayParameters::kMaxNoFeaturePeerCount) + if (++noFeaturePeerCount_ >= ledger_replay_parameters::kMaxNoFeaturePeerCount) { JLOG(journal_.debug()) << "Fall back for " << hash_; - timerInterval_ = LedgerReplayParameters::kSubTaskFallbackTimeout; + timerInterval_ = ledger_replay_parameters::kSubTaskFallbackTimeout; fallBack_ = true; } } @@ -119,7 +119,7 @@ void LedgerDeltaAcquire::onTimer(bool progress, ScopedLockType& sl) { JLOG(journal_.trace()) << "timeouts_=" << timeouts_ << " for " << hash_; - if (timeouts_ > LedgerReplayParameters::kSubTaskMaxTimeouts) + if (timeouts_ > ledger_replay_parameters::kSubTaskMaxTimeouts) { failed_ = true; JLOG(journal_.debug()) << "too many timeouts " << hash_; diff --git a/src/xrpld/app/ledger/detail/LedgerMaster.cpp b/src/xrpld/app/ledger/detail/LedgerMaster.cpp index 9a0335fc3c..83d76bcd2a 100644 --- a/src/xrpld/app/ledger/detail/LedgerMaster.cpp +++ b/src/xrpld/app/ledger/detail/LedgerMaster.cpp @@ -1042,8 +1042,8 @@ LedgerMaster::checkAccept(std::shared_ptr const& ledger) if (v->isFieldPresent(sfServerVersion)) { auto version = v->getFieldU64(sfServerVersion); - higherVersionCount += BuildInfo::isNewerVersion(version) ? 1 : 0; - xrpldCount += BuildInfo::isXrpldVersion(version) ? 1 : 0; + higherVersionCount += build_info::isNewerVersion(version) ? 1 : 0; + xrpldCount += build_info::isXrpldVersion(version) ? 1 : 0; } } // We report only if (1) we have accumulated validation messages @@ -2088,21 +2088,21 @@ LedgerMaster::makeFetchPack( if (!have) { JLOG(journal_.info()) << "Peer requests fetch pack for ledger we don't have: " << have; - peer->charge(Resource::kFeeRequestNoReply, "get_object ledger"); + peer->charge(resource::kFeeRequestNoReply, "get_object ledger"); return; } if (have->open()) { JLOG(journal_.warn()) << "Peer requests fetch pack from open ledger: " << have; - peer->charge(Resource::kFeeMalformedRequest, "get_object ledger open"); + peer->charge(resource::kFeeMalformedRequest, "get_object ledger open"); return; } if (have->header().seq < getEarliestFetch()) { JLOG(journal_.debug()) << "Peer requests fetch pack that is too early"; - peer->charge(Resource::kFeeMalformedRequest, "get_object ledger early"); + peer->charge(resource::kFeeMalformedRequest, "get_object ledger early"); return; } @@ -2112,7 +2112,7 @@ LedgerMaster::makeFetchPack( { JLOG(journal_.info()) << "Peer requests fetch pack for ledger whose predecessor we " << "don't have: " << have; - peer->charge(Resource::kFeeRequestNoReply, "get_object ledger no parent"); + peer->charge(resource::kFeeRequestNoReply, "get_object ledger no parent"); return; } diff --git a/src/xrpld/app/ledger/detail/LedgerReplayTask.cpp b/src/xrpld/app/ledger/detail/LedgerReplayTask.cpp index e7cd031247..3d7b1e0f92 100644 --- a/src/xrpld/app/ledger/detail/LedgerReplayTask.cpp +++ b/src/xrpld/app/ledger/detail/LedgerReplayTask.cpp @@ -87,18 +87,18 @@ LedgerReplayTask::LedgerReplayTask( : TimeoutCounter( app, parameter.finishHash, - LedgerReplayParameters::kTaskTimeout, + ledger_replay_parameters::kTaskTimeout, {.jobType = JtReplayTask, .jobName = "LedReplTask", - .jobLimit = LedgerReplayParameters::kMaxQueuedTasks}, + .jobLimit = ledger_replay_parameters::kMaxQueuedTasks}, app.getJournal("LedgerReplayTask")) , inboundLedgers_(inboundLedgers) , replayer_(replayer) , parameter_(parameter) , maxTimeouts_( std::max( - LedgerReplayParameters::kTaskMaxTimeoutsMinimum, - parameter.totalLedgers * LedgerReplayParameters::kTaskMaxTimeoutsMultiplier)) + ledger_replay_parameters::kTaskMaxTimeoutsMinimum, + parameter.totalLedgers * ledger_replay_parameters::kTaskMaxTimeoutsMultiplier)) , skipListAcquirer_(skipListAcquirer) { JLOG(journal_.trace()) << "Create " << hash_; diff --git a/src/xrpld/app/ledger/detail/LedgerReplayer.cpp b/src/xrpld/app/ledger/detail/LedgerReplayer.cpp index 3bb5ca9434..52184c1723 100644 --- a/src/xrpld/app/ledger/detail/LedgerReplayer.cpp +++ b/src/xrpld/app/ledger/detail/LedgerReplayer.cpp @@ -51,7 +51,7 @@ LedgerReplayer::replay( { XRPL_ASSERT( finishLedgerHash.isNonZero() && totalNumLedgers > 0 && - totalNumLedgers <= LedgerReplayParameters::kMaxTaskSize, + totalNumLedgers <= ledger_replay_parameters::kMaxTaskSize, "xrpl::LedgerReplayer::replay : valid inputs"); // NOLINTNEXTLINE(misc-const-correctness) @@ -64,7 +64,7 @@ LedgerReplayer::replay( std::scoped_lock const lock(mtx_); if (app_.isStopping()) return; - if (tasks_.size() >= LedgerReplayParameters::kMaxTasks) + if (tasks_.size() >= ledger_replay_parameters::kMaxTasks) { JLOG(j_.info()) << "Too many replay tasks, dropping new task " << parameter.finishHash; return; diff --git a/src/xrpld/app/ledger/detail/LedgerToJson.cpp b/src/xrpld/app/ledger/detail/LedgerToJson.cpp index 7a581e2389..9d3820e9f7 100644 --- a/src/xrpld/app/ledger/detail/LedgerToJson.cpp +++ b/src/xrpld/app/ledger/detail/LedgerToJson.cpp @@ -135,7 +135,7 @@ fillJsonTx( { copyFrom(txJson[jss::tx_json], txn->getJson(JsonOptions::Values::DisableApiPriorV2, false)); txJson[jss::hash] = to_string(txn->getTransactionID()); - RPC::insertDeliverMax(txJson[jss::tx_json], txnType, fill.context->apiVersion); + rpc::insertDeliverMax(txJson[jss::tx_json], txnType, fill.context->apiVersion); if (stMeta) { @@ -144,7 +144,7 @@ fillJsonTx( // If applicable, insert delivered amount if (txnType == ttPAYMENT || txnType == ttCHECK_CASH) { - RPC::insertDeliveredAmount( + rpc::insertDeliveredAmount( txJson[jss::meta], fill.ledger, txn, @@ -152,7 +152,7 @@ fillJsonTx( } // If applicable, insert mpt issuance id - RPC::insertMPTokenIssuanceID( + rpc::insertMPTokenIssuanceID( txJson[jss::meta], txn, {txn->getTransactionID(), fill.ledger.seq(), *stMeta}); } @@ -172,7 +172,7 @@ fillJsonTx( else { copyFrom(txJson, txn->getJson(JsonOptions::Values::None)); - RPC::insertDeliverMax(txJson, txnType, fill.context->apiVersion); + rpc::insertDeliverMax(txJson, txnType, fill.context->apiVersion); if (stMeta) { txJson[jss::metaData] = stMeta->getJson(JsonOptions::Values::None); @@ -180,7 +180,7 @@ fillJsonTx( // If applicable, insert delivered amount if (txnType == ttPAYMENT || txnType == ttCHECK_CASH) { - RPC::insertDeliveredAmount( + rpc::insertDeliveredAmount( txJson[jss::metaData], fill.ledger, txn, @@ -188,7 +188,7 @@ fillJsonTx( } // If applicable, insert mpt issuance id - RPC::insertMPTokenIssuanceID( + rpc::insertMPTokenIssuanceID( txJson[jss::metaData], txn, {txn->getTransactionID(), fill.ledger.seq(), *stMeta}); } } @@ -337,7 +337,7 @@ fillJson(json::Value& json, LedgerFill const& fill) fill.ledger.header(), bFull, ((fill.context != nullptr) ? fill.context->apiVersion - : RPC::kApiMaximumSupportedVersion)); + : rpc::kApiMaximumSupportedVersion)); } if (bFull || ((fill.options & static_cast(LedgerFill::Options::DumpTxrp)) != 0)) diff --git a/src/xrpld/app/ledger/detail/SkipListAcquire.cpp b/src/xrpld/app/ledger/detail/SkipListAcquire.cpp index 8ebd14083a..97b50a9a3a 100644 --- a/src/xrpld/app/ledger/detail/SkipListAcquire.cpp +++ b/src/xrpld/app/ledger/detail/SkipListAcquire.cpp @@ -37,10 +37,10 @@ SkipListAcquire::SkipListAcquire( : TimeoutCounter( app, ledgerHash, - LedgerReplayParameters::kSubTaskTimeout, + ledger_replay_parameters::kSubTaskTimeout, {.jobType = JtReplayTask, .jobName = "SkipListAcq", - .jobLimit = LedgerReplayParameters::kMaxQueuedTasks}, + .jobLimit = ledger_replay_parameters::kMaxQueuedTasks}, app.getJournal("LedgerReplaySkipList")) , inboundLedgers_(inboundLedgers) , peerSet_(std::move(peerSet)) @@ -96,10 +96,10 @@ SkipListAcquire::trigger(std::size_t limit, ScopedLockType& sl) { JLOG(journal_.trace()) << "Add a no feature peer " << peer->id() << " for " << hash_; - if (++noFeaturePeerCount_ >= LedgerReplayParameters::kMaxNoFeaturePeerCount) + if (++noFeaturePeerCount_ >= ledger_replay_parameters::kMaxNoFeaturePeerCount) { JLOG(journal_.debug()) << "Fall back for " << hash_; - timerInterval_ = LedgerReplayParameters::kSubTaskFallbackTimeout; + timerInterval_ = ledger_replay_parameters::kSubTaskFallbackTimeout; fallBack_ = true; } } @@ -114,7 +114,7 @@ void SkipListAcquire::onTimer(bool progress, ScopedLockType& sl) { JLOG(journal_.trace()) << "timeouts_=" << timeouts_ << " for " << hash_; - if (timeouts_ > LedgerReplayParameters::kSubTaskMaxTimeouts) + if (timeouts_ > ledger_replay_parameters::kSubTaskMaxTimeouts) { failed_ = true; JLOG(journal_.debug()) << "too many timeouts " << hash_; diff --git a/src/xrpld/app/main/Application.cpp b/src/xrpld/app/main/Application.cpp index 5c8fdad37c..52ec9ce544 100644 --- a/src/xrpld/app/main/Application.cpp +++ b/src/xrpld/app/main/Application.cpp @@ -230,7 +230,7 @@ public: std::optional> nodeIdentity_; ValidatorKeys const validatorKeys_; - std::unique_ptr resourceManager_; + std::unique_ptr resourceManager_; std::unique_ptr nodeStore_; NodeFamily nodeFamily_; @@ -375,7 +375,7 @@ public: , networkIDService_(std::make_unique(config_->networkId)) , validatorKeys_(*config_, journal_) , resourceManager_( - Resource::makeManager(collectorManager_->collector(), logs_->journal("Resource"))) + resource::makeManager(collectorManager_->collector(), logs_->journal("Resource"))) , nodeStore_(shaMapStore_->makeNodeStore( config_->prefetchWorkers > 0 ? config_->prefetchWorkers : 4)) , nodeFamily_(*this, *collectorManager_) @@ -673,7 +673,7 @@ public: return *loadManager_; } - Resource::Manager& + resource::Manager& getResourceManager() override { return *resourceManager_; @@ -1187,7 +1187,7 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline) logs_->threshold(Severity::Debug); } - JLOG(journal_.info()) << "Process starting: " << BuildInfo::getFullVersionString() + JLOG(journal_.info()) << "Process starting: " << build_info::getFullVersionString() << ", Instance Cookie: " << instanceCookie_; if (numberOfThreads(*config_) < 2) @@ -1457,9 +1457,9 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline) JLOG(journal_.fatal()) << "Startup RPC: " << jvCommand << std::endl; } - Resource::Charge loadType = Resource::kFeeReferenceRpc; - Resource::Consumer c; - RPC::JsonContext context{ + resource::Charge loadType = resource::kFeeReferenceRpc; + resource::Consumer c; + rpc::JsonContext context{ {.j = getJournal("RPCHandler"), .app = *this, .loadType = loadType, @@ -1469,11 +1469,11 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline) .role = Role::ADMIN, .coro = {}, .infoSub = {}, - .apiVersion = RPC::kApiMaximumSupportedVersion}, + .apiVersion = rpc::kApiMaximumSupportedVersion}, jvCommand}; json::Value jvResult; - RPC::doCommand(context, jvResult); + rpc::doCommand(context, jvResult); if (!config_->quiet()) { @@ -1489,7 +1489,7 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline) void ApplicationImp::start(bool withTimers) { - JLOG(journal_.info()) << "Application starting. Version is " << BuildInfo::getVersionString(); + JLOG(journal_.info()) << "Application starting. Version is " << build_info::getVersionString(); if (withTimers) { diff --git a/src/xrpld/app/main/CollectorManager.cpp b/src/xrpld/app/main/CollectorManager.cpp index 9e1278607f..87b5286f97 100644 --- a/src/xrpld/app/main/CollectorManager.cpp +++ b/src/xrpld/app/main/CollectorManager.cpp @@ -30,8 +30,8 @@ public: if (server == "statsd") { - beast::IP::Endpoint const address( - beast::IP::Endpoint::fromString(get(params, Keys::kAddress))); + beast::ip::Endpoint const address( + beast::ip::Endpoint::fromString(get(params, Keys::kAddress))); std::string const& prefix(get(params, Keys::kPrefix)); collector_ = beast::insight::StatsDCollector::make(address, prefix, journal); diff --git a/src/xrpld/app/main/GRPCServer.cpp b/src/xrpld/app/main/GRPCServer.cpp index 1146cbdc08..1b20ff1d49 100644 --- a/src/xrpld/app/main/GRPCServer.cpp +++ b/src/xrpld/app/main/GRPCServer.cpp @@ -71,10 +71,10 @@ getEndpoint(std::string const& peer) peerClean = peer.substr(first + 1); } - std::optional endpoint = - beast::IP::Endpoint::fromStringChecked(peerClean); + std::optional endpoint = + beast::ip::Endpoint::fromStringChecked(peerClean); if (endpoint) - return beast::IP::toAsioEndpoint(endpoint.value()); + return beast::ip::toAsioEndpoint(endpoint.value()); } catch (std::exception const&) // NOLINT(bugprone-empty-catch) { @@ -92,8 +92,8 @@ GRPCServerImpl::CallData::CallData( BindListener bindListener, Handler handler, Forward forward, - RPC::Condition requiredCondition, - Resource::Charge loadType, + rpc::Condition requiredCondition, + resource::Charge loadType, std::vector const& secureGatewayIPs) : service_(service) , cq_(cq) @@ -195,7 +195,7 @@ GRPCServerImpl::CallData::process(std::shared_ptr context{ + rpc::GRPCContext context{ {app_.getJournal("gRPCServer"), app_, loadType, @@ -209,11 +209,11 @@ GRPCServerImpl::CallData::process(std::shared_ptr::isFinished() } template -Resource::Charge +resource::Charge GRPCServerImpl::CallData::getLoadType() { return loadType_; @@ -323,12 +323,12 @@ GRPCServerImpl::CallData::setIsUnlimited(Response& response, } template -Resource::Consumer +resource::Consumer GRPCServerImpl::CallData::getUsage() { auto endpoint = getClientEndpoint(); if (endpoint) - return app_.getResourceManager().newInboundEndpoint(beast::IP::fromAsio(endpoint.value())); + return app_.getResourceManager().newInboundEndpoint(beast::ip::fromAsio(endpoint.value())); Throw("Failed to get client endpoint"); } @@ -527,7 +527,7 @@ GRPCServerImpl::handleRpcs() std::vector> GRPCServerImpl::setupListeners() { - using RPC::Condition; + using rpc::Condition; std::vector> requests; auto addToRequests = [&requests](auto callData) { requests.push_back(std::move(callData)); }; @@ -545,7 +545,7 @@ GRPCServerImpl::setupListeners() doLedgerGrpc, &org::xrpl::rpc::v1::XRPLedgerAPIService::Stub::GetLedger, Condition::NoCondition, - Resource::kFeeMediumBurdenRpc, + resource::kFeeMediumBurdenRpc, secureGatewayIPs_)); } { @@ -562,7 +562,7 @@ GRPCServerImpl::setupListeners() doLedgerDataGrpc, &org::xrpl::rpc::v1::XRPLedgerAPIService::Stub::GetLedgerData, Condition::NoCondition, - Resource::kFeeMediumBurdenRpc, + resource::kFeeMediumBurdenRpc, secureGatewayIPs_)); } { @@ -579,7 +579,7 @@ GRPCServerImpl::setupListeners() doLedgerDiffGrpc, &org::xrpl::rpc::v1::XRPLedgerAPIService::Stub::GetLedgerDiff, Condition::NoCondition, - Resource::kFeeMediumBurdenRpc, + resource::kFeeMediumBurdenRpc, secureGatewayIPs_)); } { @@ -596,7 +596,7 @@ GRPCServerImpl::setupListeners() doLedgerEntryGrpc, &org::xrpl::rpc::v1::XRPLedgerAPIService::Stub::GetLedgerEntry, Condition::NoCondition, - Resource::kFeeMediumBurdenRpc, + resource::kFeeMediumBurdenRpc, secureGatewayIPs_)); } return requests; diff --git a/src/xrpld/app/main/GRPCServer.h b/src/xrpld/app/main/GRPCServer.h index db948cab99..98b50fcd0c 100644 --- a/src/xrpld/app/main/GRPCServer.h +++ b/src/xrpld/app/main/GRPCServer.h @@ -102,7 +102,7 @@ private: // typedef for actual handler (that populates a response) // handlers are defined in rpc/GRPCHandlers.h template - using Handler = std::function(RPC::GRPCContext&)>; + using Handler = std::function(rpc::GRPCContext&)>; // This implementation is currently limited to v1 of the API static constexpr unsigned kApiVersion = 1; @@ -189,10 +189,10 @@ private: Forward forward_; // Condition required for this RPC - RPC::Condition requiredCondition_; + rpc::Condition requiredCondition_; // Load type for this RPC - Resource::Charge loadType_; + resource::Charge loadType_; std::vector const& secureGatewayIPs_; @@ -209,8 +209,8 @@ private: BindListener bindListener, Handler handler, Forward forward, - RPC::Condition requiredCondition, - Resource::Charge loadType, + rpc::Condition requiredCondition, + resource::Charge loadType, std::vector const& secureGatewayIPs); CallData(CallData const&) = delete; @@ -233,7 +233,7 @@ private: process(std::shared_ptr coro); // return load type of this RPC - Resource::Charge + resource::Charge getLoadType(); // return the Role used for this RPC @@ -241,7 +241,7 @@ private: getRole(bool isUnlimited); // register endpoint with ResourceManager and return usage - Resource::Consumer + resource::Consumer getUsage(); // Returns the ip of the client @@ -290,7 +290,7 @@ private: // forward request to a p2p node void - forwardToP2p(RPC::GRPCContext& context); + forwardToP2p(rpc::GRPCContext& context); }; // CallData diff --git a/src/xrpld/app/main/Main.cpp b/src/xrpld/app/main/Main.cpp index d0b40efce8..a23b84f2e8 100644 --- a/src/xrpld/app/main/Main.cpp +++ b/src/xrpld/app/main/Main.cpp @@ -506,7 +506,7 @@ run(int argc, char** argv) if (vm.contains("version")) { // LCOV_EXCL_START - std::cout << "xrpld version " << BuildInfo::getVersionString() << std::endl; + std::cout << "xrpld version " << build_info::getVersionString() << std::endl; std::cout << "Git commit hash: " << xrpl::git::getCommitHash() << std::endl; std::cout << "Git build branch: " << xrpl::git::getBuildBranch() << std::endl; return 0; @@ -716,7 +716,7 @@ run(int argc, char** argv) // happen after the config file is loaded. if (vm.contains("rpc_ip")) { - auto endpoint = beast::IP::Endpoint::fromStringChecked(vm["rpc_ip"].as()); + auto endpoint = beast::ip::Endpoint::fromStringChecked(vm["rpc_ip"].as()); if (!endpoint) { std::cerr << "Invalid rpc_ip = " << vm["rpc_ip"].as() << "\n"; @@ -826,7 +826,7 @@ run(int argc, char** argv) // We have an RPC command to process: beast::setCurrentThreadName("xrpld: rpc"); - return RPCCall::fromCommandLine( + return rpc_call::fromCommandLine( *config, vm["parameters"].as>(), *logs); // LCOV_EXCL_STOP } diff --git a/src/xrpld/app/misc/DeliverMax.h b/src/xrpld/app/misc/DeliverMax.h index 73ccc95800..1683219c8b 100644 --- a/src/xrpld/app/misc/DeliverMax.h +++ b/src/xrpld/app/misc/DeliverMax.h @@ -6,7 +6,7 @@ namespace json { class Value; } // namespace json -namespace xrpl::RPC { +namespace xrpl::rpc { /** * Copy `Amount` field to `DeliverMax` field in transaction output JSON. @@ -22,4 +22,4 @@ insertDeliverMax(json::Value& txJson, TxType txnType, unsigned int apiVersion); /** @} */ -} // namespace xrpl::RPC +} // namespace xrpl::rpc diff --git a/src/xrpld/app/misc/NetworkOPs.cpp b/src/xrpld/app/misc/NetworkOPs.cpp index 47cbebb901..8f31ce1eb3 100644 --- a/src/xrpld/app/misc/NetworkOPs.cpp +++ b/src/xrpld/app/misc/NetworkOPs.cpp @@ -1215,7 +1215,7 @@ NetworkOPsImp::processClusterTimer() n.set_nodename(node.name()); }); - Resource::Gossip const gossip = registry_.get().getResourceManager().exportConsumers(); + resource::Gossip const gossip = registry_.get().getResourceManager().exportConsumers(); for (auto& item : gossip.items) { protocol::TMLoadSource& node = *cluster.add_loadsources(); @@ -2487,7 +2487,7 @@ NetworkOPsImp::pubValidation(std::shared_ptr const& val) // for consumers supporting different API versions MultiApiJson multiObj{jvObj}; multiObj.visit( - RPC::kApiVersion<1>, // + rpc::kApiVersion<1>, // [](json::Value& jvTx) { // Type conversion for older API versions to string if (jvTx.isMember(jss::ledger_index)) @@ -2688,7 +2688,7 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters) if (!registry_.get().getApp().config().serverDomain.empty()) info[jss::server_domain] = registry_.get().getApp().config().serverDomain; - info[jss::build_version] = BuildInfo::getVersionString(); + info[jss::build_version] = build_info::getVersionString(); info[jss::server_state] = strOperatingMode(admin); @@ -3167,7 +3167,7 @@ NetworkOPsImp::pubLedger(std::shared_ptr const& lpAccepted) if (!streamMaps_[SBookChanges].empty()) { - json::Value const jvObj = xrpl::RPC::computeBookChanges(lpAccepted); + json::Value const jvObj = xrpl::rpc::computeBookChanges(lpAccepted); auto it = streamMaps_[SBookChanges].begin(); while (it != streamMaps_[SBookChanges].end()) @@ -3281,9 +3281,9 @@ NetworkOPsImp::transJson( if (meta) { jvObj[jss::meta] = meta->get().getJson(JsonOptions::Values::None); - RPC::insertDeliveredAmount(jvObj[jss::meta], *ledger, transaction, meta->get()); - RPC::insertNFTSyntheticInJson(jvObj, transaction, meta->get()); - RPC::insertMPTokenIssuanceID(jvObj[jss::meta], transaction, meta->get()); + rpc::insertDeliveredAmount(jvObj[jss::meta], *ledger, transaction, meta->get()); + rpc::insertNFTSyntheticInJson(jvObj, transaction, meta->get()); + rpc::insertMPTokenIssuanceID(jvObj[jss::meta], transaction, meta->get()); } // add CTID where the needed data for it exists @@ -3295,7 +3295,7 @@ NetworkOPsImp::transJson( if (transaction->isFieldPresent(sfNetworkID)) netID = transaction->getFieldU32(sfNetworkID); - if (std::optional ctid = RPC::encodeCTID(ledger->header().seq, txnSeq, netID); + if (std::optional ctid = rpc::encodeCTID(ledger->header().seq, txnSeq, netID); ctid) jvObj[jss::ctid] = *ctid; } @@ -3346,7 +3346,7 @@ NetworkOPsImp::transJson( forAllApiVersions( multiObj.visit(), // [&](json::Value& jvTx, std::integral_constant) { - RPC::insertDeliverMax(jvTx[jss::transaction], transaction->getTxnType(), Version); + rpc::insertDeliverMax(jvTx[jss::transaction], transaction->getTxnType(), Version); if constexpr (Version > 1) { @@ -3891,7 +3891,7 @@ NetworkOPsImp::addAccountHistoryJob(SubAccountHistoryInfoWeak subInfo) int feeChargeCount = 0; if (auto sptr = subInfo.sinkWptr.lock(); sptr) { - sptr->getConsumer().charge(Resource::kFeeMediumBurdenRpc); + sptr->getConsumer().charge(resource::kFeeMediumBurdenRpc); ++feeChargeCount; } else diff --git a/src/xrpld/app/misc/detail/DeliverMax.cpp b/src/xrpld/app/misc/detail/DeliverMax.cpp index add3cf89ee..e512b078c7 100644 --- a/src/xrpld/app/misc/detail/DeliverMax.cpp +++ b/src/xrpld/app/misc/detail/DeliverMax.cpp @@ -3,7 +3,7 @@ #include #include -namespace xrpl::RPC { +namespace xrpl::rpc { void insertDeliverMax(json::Value& txJson, TxType txnType, unsigned int apiVersion) @@ -19,4 +19,4 @@ insertDeliverMax(json::Value& txJson, TxType txnType, unsigned int apiVersion) } } -} // namespace xrpl::RPC +} // namespace xrpl::rpc diff --git a/src/xrpld/app/misc/detail/Transaction.cpp b/src/xrpld/app/misc/detail/Transaction.cpp index e29181bfe9..59cc4b6c4c 100644 --- a/src/xrpld/app/misc/detail/Transaction.cpp +++ b/src/xrpld/app/misc/detail/Transaction.cpp @@ -182,7 +182,7 @@ Transaction::getJson(JsonOptions options, bool binary) const if (txnSeq_ && netID) { - std::optional const ctid = RPC::encodeCTID(ledgerIndex_, *txnSeq_, *netID); + std::optional const ctid = rpc::encodeCTID(ledgerIndex_, *txnSeq_, *netID); if (ctid) ret[jss::ctid] = *ctid; } diff --git a/src/xrpld/app/misc/detail/WorkBase.h b/src/xrpld/app/misc/detail/WorkBase.h index 73e5081036..fd36cb5318 100644 --- a/src/xrpld/app/misc/detail/WorkBase.h +++ b/src/xrpld/app/misc/detail/WorkBase.h @@ -223,7 +223,7 @@ WorkBase::onStart() req_.target(path_.empty() ? "/" : path_); req_.version(11); req_.set("Host", host_ + ":" + port_); - req_.set("User-Agent", BuildInfo::getFullVersionString()); + req_.set("User-Agent", build_info::getFullVersionString()); req_.prepare_payload(); boost::beast::http::async_write( impl().stream(), diff --git a/src/xrpld/app/rdb/PeerFinder.h b/src/xrpld/app/rdb/PeerFinder.h index 5d916000a3..3ff7b7268b 100644 --- a/src/xrpld/app/rdb/PeerFinder.h +++ b/src/xrpld/app/rdb/PeerFinder.h @@ -44,6 +44,6 @@ readPeerFinderDB(soci::session& session, std::function const& v); +savePeerFinderDB(soci::session& session, std::vector const& v); } // namespace xrpl diff --git a/src/xrpld/app/rdb/detail/PeerFinder.cpp b/src/xrpld/app/rdb/detail/PeerFinder.cpp index 72a275c7cd..8d9af69ae3 100644 --- a/src/xrpld/app/rdb/detail/PeerFinder.cpp +++ b/src/xrpld/app/rdb/detail/PeerFinder.cpp @@ -108,7 +108,7 @@ updatePeerFinderDB(soci::session& session, int currentSchemaVersion, beast::Jour std::size_t count = 0; session << "SELECT COUNT(*) FROM PeerFinder_BootstrapCache;", soci::into(count); - std::vector list; + std::vector list; { list.reserve(count); @@ -125,8 +125,8 @@ updatePeerFinderDB(soci::session& session, int currentSchemaVersion, beast::Jour st.execute(); while (st.fetch()) { - PeerFinder::Store::Entry entry; - entry.endpoint = beast::IP::Endpoint::fromString(s); + peer_finder::Store::Entry entry; + entry.endpoint = beast::ip::Endpoint::fromString(s); if (!isUnspecified(entry.endpoint)) { entry.valence = valence; @@ -226,7 +226,7 @@ readPeerFinderDB(soci::session& session, std::function const& v) +savePeerFinderDB(soci::session& session, std::vector const& v) { soci::transaction tr(session); session << "DELETE FROM PeerFinder_BootstrapCache;"; diff --git a/src/xrpld/core/Config.h b/src/xrpld/core/Config.h index 852e46218a..d43a7a566d 100644 --- a/src/xrpld/core/Config.h +++ b/src/xrpld/core/Config.h @@ -277,7 +277,7 @@ public: std::size_t txRelayPercentage = 25; // These override the command line client settings - std::optional rpcIp; + std::optional rpcIp; std::unordered_set> features; diff --git a/src/xrpld/overlay/Overlay.h b/src/xrpld/overlay/Overlay.h index 6cc229f5a0..9ab80e6697 100644 --- a/src/xrpld/overlay/Overlay.h +++ b/src/xrpld/overlay/Overlay.h @@ -55,7 +55,7 @@ public: explicit Setup() = default; std::shared_ptr context; - beast::IP::Address publicIp; + beast::ip::Address publicIp; int ipLimit = 0; std::uint32_t crawlOptions = 0; std::optional networkID; @@ -92,7 +92,7 @@ public: * performed asynchronously. */ virtual void - connect(beast::IP::Endpoint const& address) = 0; + connect(beast::ip::Endpoint const& address) = 0; /** * Returns the maximum number of peers we are configured to allow. diff --git a/src/xrpld/overlay/Peer.h b/src/xrpld/overlay/Peer.h index 20a8730cf1..c2631cc7ce 100644 --- a/src/xrpld/overlay/Peer.h +++ b/src/xrpld/overlay/Peer.h @@ -15,9 +15,9 @@ namespace xrpl { -namespace Resource { +namespace resource { class Charge; -} // namespace Resource +} // namespace resource enum class ProtocolFeature { ValidatorListPropagation, @@ -51,7 +51,7 @@ public: virtual void send(std::shared_ptr const& m) = 0; - [[nodiscard]] virtual beast::IP::Endpoint + [[nodiscard]] virtual beast::ip::Endpoint getRemoteAddress() const = 0; /** @@ -76,7 +76,7 @@ public: * Adjust this peer's load balance based on the type of load imposed. */ virtual void - charge(Resource::Charge const& fee, std::string const& context) = 0; + charge(resource::Charge const& fee, std::string const& context) = 0; // // Identity diff --git a/src/xrpld/overlay/detail/ConnectAttempt.cpp b/src/xrpld/overlay/detail/ConnectAttempt.cpp index 0f0b3242de..b78b8eb7b8 100644 --- a/src/xrpld/overlay/detail/ConnectAttempt.cpp +++ b/src/xrpld/overlay/detail/ConnectAttempt.cpp @@ -49,10 +49,10 @@ ConnectAttempt::ConnectAttempt( Application& app, boost::asio::io_context& ioContext, endpoint_type remoteEndpoint, - Resource::Consumer usage, + resource::Consumer usage, shared_context const& context, Peer::id_t id, - std::shared_ptr const& slot, + std::shared_ptr const& slot, beast::Journal journal, OverlayImpl& overlay) : Child(overlay) @@ -462,7 +462,7 @@ ConnectAttempt::processResponse() auto const result = overlay_.peerFinder().activate(slot_, publicKey, static_cast(member)); - if (result != PeerFinder::Result::Success) + if (result != peer_finder::Result::Success) { fail("Outbound " + std::string(to_string(result))); return; diff --git a/src/xrpld/overlay/detail/ConnectAttempt.h b/src/xrpld/overlay/detail/ConnectAttempt.h index f9ba33571f..3ebffc529d 100644 --- a/src/xrpld/overlay/detail/ConnectAttempt.h +++ b/src/xrpld/overlay/detail/ConnectAttempt.h @@ -41,7 +41,7 @@ private: beast::WrappedSink sink_; beast::Journal const journal_; endpoint_type remoteEndpoint_; - Resource::Consumer usage_; + resource::Consumer usage_; boost::asio::strand strand_; boost::asio::basic_waitable_timer timer_; std::unique_ptr streamPtr_; @@ -49,7 +49,7 @@ private: stream_type& stream_; boost::beast::multi_buffer readBuf_; response_type response_; - std::shared_ptr slot_; + std::shared_ptr slot_; request_type req_; public: @@ -57,10 +57,10 @@ public: Application& app, boost::asio::io_context& ioContext, endpoint_type remoteEndpoint, - Resource::Consumer usage, + resource::Consumer usage, shared_context const& context, Peer::id_t id, - std::shared_ptr const& slot, + std::shared_ptr const& slot, beast::Journal journal, OverlayImpl& overlay); @@ -102,7 +102,7 @@ private: static boost::asio::ip::tcp::endpoint parseEndpoint(std::string const& s, boost::system::error_code& ec) { - beast::IP::Endpoint bep; + beast::ip::Endpoint bep; std::istringstream is(s); is >> bep; if (is.fail()) diff --git a/src/xrpld/overlay/detail/Handshake.cpp b/src/xrpld/overlay/detail/Handshake.cpp index a860d2d604..a12923d1c3 100644 --- a/src/xrpld/overlay/detail/Handshake.cpp +++ b/src/xrpld/overlay/detail/Handshake.cpp @@ -186,8 +186,8 @@ buildHandshake( boost::beast::http::fields& h, xrpl::uint256 const& sharedValue, std::optional networkID, - beast::IP::Address publicIp, - beast::IP::Address remoteIp, + beast::ip::Address publicIp, + beast::ip::Address remoteIp, Application& app) { if (networkID) @@ -213,7 +213,7 @@ buildHandshake( if (!app.config().serverDomain.empty()) h.insert("Server-Domain", app.config().serverDomain); - if (beast::IP::isPublic(remoteIp)) + if (beast::ip::isPublic(remoteIp)) h.insert("Remote-IP", remoteIp.to_string()); if (!publicIp.is_unspecified()) @@ -231,8 +231,8 @@ verifyHandshake( boost::beast::http::fields const& headers, xrpl::uint256 const& sharedValue, std::optional networkID, - beast::IP::Address publicIp, - beast::IP::Address remote, + beast::ip::Address publicIp, + beast::ip::Address remote, Application& app) { if (auto const iter = headers.find("Server-Domain"); iter != headers.end()) @@ -331,7 +331,7 @@ verifyHandshake( if (ec) throw std::runtime_error("Invalid Local-IP"); - if (beast::IP::isPublic(remote) && remote != localIp) + if (beast::ip::isPublic(remote) && remote != localIp) { throw std::runtime_error( "Incorrect Local-IP: " + remote.to_string() + " instead of " + localIp.to_string()); @@ -346,7 +346,7 @@ verifyHandshake( if (ec) throw std::runtime_error("Invalid Remote-IP"); - if (beast::IP::isPublic(remote) && !beast::IP::isUnspecified(publicIp)) + if (beast::ip::isPublic(remote) && !beast::ip::isUnspecified(publicIp)) { // We know our public IP and peer reports our connection came // from some other IP. @@ -374,7 +374,7 @@ makeRequest( m.method(boost::beast::http::verb::get); m.target("/"); m.version(11); - m.insert("User-Agent", BuildInfo::getFullVersionString()); + m.insert("User-Agent", build_info::getFullVersionString()); m.insert("Upgrade", supportedProtocolVersions()); m.insert("Connection", "Upgrade"); m.insert("Connect-As", "Peer"); @@ -390,8 +390,8 @@ http_response_type makeResponse( bool crawlPublic, http_request_type const& req, - beast::IP::Address publicIp, - beast::IP::Address remoteIp, + beast::ip::Address publicIp, + beast::ip::Address remoteIp, uint256 const& sharedValue, std::optional networkID, ProtocolVersion protocol, @@ -403,7 +403,7 @@ makeResponse( resp.insert("Connection", "Upgrade"); resp.insert("Upgrade", to_string(protocol)); resp.insert("Connect-As", "Peer"); - resp.insert("Server", BuildInfo::getFullVersionString()); + resp.insert("Server", build_info::getFullVersionString()); resp.insert("Crawl", crawlPublic ? "public" : "private"); resp.insert( "X-Protocol-Ctl", diff --git a/src/xrpld/overlay/detail/Handshake.h b/src/xrpld/overlay/detail/Handshake.h index 9a4e5ba507..d54cd3a0ea 100644 --- a/src/xrpld/overlay/detail/Handshake.h +++ b/src/xrpld/overlay/detail/Handshake.h @@ -47,8 +47,8 @@ buildHandshake( boost::beast::http::fields& h, uint256 const& sharedValue, std::optional networkID, - beast::IP::Address publicIp, - beast::IP::Address remoteIp, + beast::ip::Address publicIp, + beast::ip::Address remoteIp, Application& app); /** @@ -68,8 +68,8 @@ verifyHandshake( boost::beast::http::fields const& headers, uint256 const& sharedValue, std::optional networkID, - beast::IP::Address publicIp, - beast::IP::Address remote, + beast::ip::Address publicIp, + beast::ip::Address remote, Application& app); /** @@ -109,8 +109,8 @@ http_response_type makeResponse( bool crawlPublic, http_request_type const& req, - beast::IP::Address publicIp, - beast::IP::Address remoteIp, + beast::ip::Address publicIp, + beast::ip::Address remoteIp, uint256 const& sharedValue, std::optional networkID, ProtocolVersion version, diff --git a/src/xrpld/overlay/detail/OverlayImpl.cpp b/src/xrpld/overlay/detail/OverlayImpl.cpp index f9972548d1..81ead14111 100644 --- a/src/xrpld/overlay/detail/OverlayImpl.cpp +++ b/src/xrpld/overlay/detail/OverlayImpl.cpp @@ -93,13 +93,13 @@ namespace xrpl { -namespace CrawlOptions { +namespace crawl_options { static constexpr auto kDisabled = 0; static constexpr auto kOverlay = (1 << 0); static constexpr auto kServerInfo = (1 << 1); static constexpr auto kServerCounts = (1 << 2); static constexpr auto kUnl = (1 << 3); -} // namespace CrawlOptions +} // namespace crawl_options //------------------------------------------------------------------------------ @@ -155,7 +155,7 @@ OverlayImpl::Timer::onTimer(error_code ec) if (overlay_.app_.config().txReduceRelayEnable) overlay_.sendTxQueue(); - if ((++overlay_.timerCount_ % Tuning::kCheckIdlePeers) == 0) + if ((++overlay_.timerCount_ % tuning::kCheckIdlePeers) == 0) overlay_.deleteIdlePeers(); asyncWait(); @@ -167,7 +167,7 @@ OverlayImpl::OverlayImpl( Application& app, Setup setup, ServerHandler& serverHandler, - Resource::Manager& resourceManager, + resource::Manager& resourceManager, Resolver& resolver, boost::asio::io_context& ioContext, BasicConfig const& config, @@ -182,7 +182,7 @@ OverlayImpl::OverlayImpl( , resourceManager_(resourceManager) , store_(app_.getJournal("PeerFinder")) , peerFinder_( - PeerFinder::makeManager( + peer_finder::makeManager( ioContext, stopwatch(), app_.getJournal("PeerFinder"), @@ -308,7 +308,7 @@ OverlayImpl::onHandoff( bool const reserved = static_cast(app_.getCluster().member(publicKey)) || app_.getPeerReservations().contains(publicKey); auto const result = peerFinder_->activate(slot, publicKey, reserved); - if (result != PeerFinder::Result::Success) + if (result != peer_finder::Result::Success) { peerFinder_->onClosed(slot); JLOG(journal.debug()) @@ -381,14 +381,14 @@ OverlayImpl::makePrefix(std::uint32_t id) std::shared_ptr OverlayImpl::makeRedirectResponse( - std::shared_ptr const& slot, + std::shared_ptr const& slot, http_request_type const& request, address_type remoteAddress) { boost::beast::http::response msg; msg.version(request.version()); msg.result(boost::beast::http::status::service_unavailable); - msg.insert("Server", BuildInfo::getFullVersionString()); + msg.insert("Server", build_info::getFullVersionString()); { std::ostringstream ostr; ostr << remoteAddress; @@ -408,7 +408,7 @@ OverlayImpl::makeRedirectResponse( std::shared_ptr OverlayImpl::makeErrorResponse( - std::shared_ptr const& slot, + std::shared_ptr const& slot, http_request_type const& request, address_type remoteAddress, std::string const& text) @@ -417,7 +417,7 @@ OverlayImpl::makeErrorResponse( msg.version(request.version()); msg.result(boost::beast::http::status::bad_request); msg.reason("Bad Request (" + text + ")"); - msg.insert("Server", BuildInfo::getFullVersionString()); + msg.insert("Server", build_info::getFullVersionString()); msg.insert("Remote-Address", remoteAddress.to_string()); msg.insert(boost::beast::http::field::connection, "close"); msg.prepare_payload(); @@ -427,7 +427,7 @@ OverlayImpl::makeErrorResponse( //------------------------------------------------------------------------------ void -OverlayImpl::connect(beast::IP::Endpoint const& remoteEndpoint) +OverlayImpl::connect(beast::ip::Endpoint const& remoteEndpoint) { XRPL_ASSERT(work_, "xrpl::OverlayImpl::connect : work is set"); @@ -497,7 +497,7 @@ OverlayImpl::addActive(std::shared_ptr const& peer) } void -OverlayImpl::remove(std::shared_ptr const& slot) +OverlayImpl::remove(std::shared_ptr const& slot) { std::scoped_lock const lock(mutex_); auto const iter = peers_.find(slot); @@ -508,7 +508,7 @@ OverlayImpl::remove(std::shared_ptr const& slot) void OverlayImpl::start() { - PeerFinder::Config const config = PeerFinder::makeConfig( + peer_finder::Config const config = peer_finder::makeConfig( app_.config(), serverHandler_.setup().overlay.port(), app_.getValidationPublicKey().has_value(), @@ -541,7 +541,7 @@ OverlayImpl::start() resolver_.resolve( bootstrapIps, - [this](std::string const& name, std::vector const& addresses) { + [this](std::string const& name, std::vector const& addresses) { std::vector ips; ips.reserve(addresses.size()); for (auto const& addr : addresses) @@ -566,8 +566,8 @@ OverlayImpl::start() { resolver_.resolve( app_.config().ipsFixed, - [this](std::string const& name, std::vector const& addresses) { - std::vector ips; + [this](std::string const& name, std::vector const& addresses) { + std::vector ips; ips.reserve(addresses.size()); for (auto& addr : addresses) @@ -868,30 +868,30 @@ OverlayImpl::json() bool OverlayImpl::processCrawl(http_request_type const& req, Handoff& handoff) { - if (req.target() != "/crawl" || setup_.crawlOptions == CrawlOptions::kDisabled) + if (req.target() != "/crawl" || setup_.crawlOptions == crawl_options::kDisabled) return false; boost::beast::http::response msg; msg.version(req.version()); msg.result(boost::beast::http::status::ok); - msg.insert("Server", BuildInfo::getFullVersionString()); + msg.insert("Server", build_info::getFullVersionString()); msg.insert("Content-Type", "application/json"); msg.insert("Connection", "close"); msg.body()["version"] = json::Value(2u); - if ((setup_.crawlOptions & CrawlOptions::kOverlay) != 0u) + if ((setup_.crawlOptions & crawl_options::kOverlay) != 0u) { msg.body()["overlay"] = getOverlayInfo(); } - if ((setup_.crawlOptions & CrawlOptions::kServerInfo) != 0u) + if ((setup_.crawlOptions & crawl_options::kServerInfo) != 0u) { msg.body()["server"] = getServerInfo(); } - if ((setup_.crawlOptions & CrawlOptions::kServerCounts) != 0u) + if ((setup_.crawlOptions & crawl_options::kServerCounts) != 0u) { msg.body()["counts"] = getServerCounts(); } - if ((setup_.crawlOptions & CrawlOptions::kUnl) != 0u) + if ((setup_.crawlOptions & crawl_options::kUnl) != 0u) { msg.body()["unl"] = getUnlInfo(); } @@ -915,7 +915,7 @@ OverlayImpl::processValidatorList(http_request_type const& req, Handoff& handoff boost::beast::http::response msg; msg.version(req.version()); - msg.insert("Server", BuildInfo::getFullVersionString()); + msg.insert("Server", build_info::getFullVersionString()); msg.insert("Content-Type", "application/json"); msg.insert("Connection", "close"); @@ -972,7 +972,7 @@ OverlayImpl::processHealth(http_request_type const& req, Handoff& handoff) return false; boost::beast::http::response msg; msg.version(req.version()); - msg.insert("Server", BuildInfo::getFullVersionString()); + msg.insert("Server", build_info::getFullVersionString()); msg.insert("Content-Type", "application/json"); msg.insert("Connection", "close"); @@ -1535,7 +1535,7 @@ setupOverlay(BasicConfig const& config, beast::Journal j) { boost::system::error_code ec; setup.publicIp = boost::asio::ip::make_address(ip, ec); - if (ec || !beast::IP::isPublic(setup.publicIp)) + if (ec || !beast::ip::isPublic(setup.publicIp)) Throw("Configured public IP is invalid"); } @@ -1577,19 +1577,19 @@ setupOverlay(BasicConfig const& config, beast::Journal j) { if (get(section, Keys::kOverlay, true)) { - setup.crawlOptions |= CrawlOptions::kOverlay; + setup.crawlOptions |= crawl_options::kOverlay; } if (get(section, Keys::kServer, true)) { - setup.crawlOptions |= CrawlOptions::kServerInfo; + setup.crawlOptions |= crawl_options::kServerInfo; } if (get(section, Keys::kCounts, false)) { - setup.crawlOptions |= CrawlOptions::kServerCounts; + setup.crawlOptions |= crawl_options::kServerCounts; } if (get(section, Keys::kUnl, true)) { - setup.crawlOptions |= CrawlOptions::kUnl; + setup.crawlOptions |= crawl_options::kUnl; } } } @@ -1632,7 +1632,7 @@ makeOverlay( Application& app, Overlay::Setup const& setup, ServerHandler& serverHandler, - Resource::Manager& resourceManager, + resource::Manager& resourceManager, Resolver& resolver, boost::asio::io_context& ioContext, BasicConfig const& config, diff --git a/src/xrpld/overlay/detail/OverlayImpl.h b/src/xrpld/overlay/detail/OverlayImpl.h index cd2c7d630b..f274bbba5a 100644 --- a/src/xrpld/overlay/detail/OverlayImpl.h +++ b/src/xrpld/overlay/detail/OverlayImpl.h @@ -109,11 +109,11 @@ private: Setup setup_; beast::Journal const journal_; ServerHandler& serverHandler_; - Resource::Manager& resourceManager_; - PeerFinder::StoreSqdb store_; - std::unique_ptr peerFinder_; + resource::Manager& resourceManager_; + peer_finder::StoreSqdb store_; + std::unique_ptr peerFinder_; TrafficCount traffic_; - hash_map, std::weak_ptr> peers_; + hash_map, std::weak_ptr> peers_; hash_map> ids_; Resolver& resolver_; std::atomic nextId_; @@ -141,7 +141,7 @@ public: Application& app, Setup setup, ServerHandler& serverHandler, - Resource::Manager& resourceManager, + resource::Manager& resourceManager, Resolver& resolver, boost::asio::io_context& ioContext, BasicConfig const& config, @@ -157,13 +157,13 @@ public: void stop() override; - PeerFinder::Manager& + peer_finder::Manager& peerFinder() { return *peerFinder_; } - Resource::Manager& + resource::Manager& resourceManager() { return resourceManager_; @@ -182,7 +182,7 @@ public: endpoint_type remoteEndpoint) override; void - connect(beast::IP::Endpoint const& remoteEndpoint) override; + connect(beast::ip::Endpoint const& remoteEndpoint) override; int limit() override; @@ -252,7 +252,7 @@ public: addActive(std::shared_ptr const& peer); void - remove(std::shared_ptr const& slot); + remove(std::shared_ptr const& slot); /** * Called when a peer has connected successfully @@ -451,13 +451,13 @@ private: std::shared_ptr makeRedirectResponse( - std::shared_ptr const& slot, + std::shared_ptr const& slot, http_request_type const& request, address_type remoteAddress); static std::shared_ptr makeErrorResponse( - std::shared_ptr const& slot, + std::shared_ptr const& slot, http_request_type const& request, address_type remoteAddress, std::string const& msg); diff --git a/src/xrpld/overlay/detail/PeerImp.cpp b/src/xrpld/overlay/detail/PeerImp.cpp index 688d0ac314..ca6fb180d4 100644 --- a/src/xrpld/overlay/detail/PeerImp.cpp +++ b/src/xrpld/overlay/detail/PeerImp.cpp @@ -125,11 +125,11 @@ constexpr std::chrono::seconds kPeerTimerInterval{60}; PeerImp::PeerImp( Application& app, id_t id, - std::shared_ptr const& slot, + std::shared_ptr const& slot, http_request_type&& request, PublicKey const& publicKey, ProtocolVersion protocol, - Resource::Consumer consumer, + resource::Consumer consumer, std::unique_ptr&& streamPtr, OverlayImpl& overlay) : Child(overlay) @@ -157,7 +157,7 @@ PeerImp::PeerImp( , creationTime_(clock_type::now()) , squelch_(app_.getJournal("Squelch")) , usage_(consumer) - , fee_{.fee = Resource::kFeeTrivialPeer, .context = ""} + , fee_{.fee = resource::kFeeTrivialPeer, .context = ""} , slot_(slot) , request_(std::move(request)) , headers_(request_) @@ -303,7 +303,7 @@ PeerImp::send(std::shared_ptr const& m) auto sendqSize = self->sendQueue_.size(); - if (sendqSize < Tuning::kTargetSendQueue) + if (sendqSize < tuning::kTargetSendQueue) { // To detect a peer that does not read from their // side of the connection, we expect a peer to have @@ -312,7 +312,7 @@ PeerImp::send(std::shared_ptr const& m) } else if ( auto sink = self->journal_.debug(); - sink && (sendqSize % Tuning::kSendQueueLogFreq) == 0) + sink && (sendqSize % tuning::kSendQueueLogFreq) == 0) { std::string const n = self->name(); sink << n << " sendq: " << sendqSize; @@ -374,10 +374,10 @@ PeerImp::removeTxQueue(uint256 const& hash) } void -PeerImp::charge(Resource::Charge const& fee, std::string const& context) +PeerImp::charge(resource::Charge const& fee, std::string const& context) { dispatch(strand_, [self = shared_from_this(), fee, context]() { - if ((self->usage_.charge(fee, context) == Resource::Disposition::Drop) && + if ((self->usage_.charge(fee, context) == resource::Disposition::Drop) && self->usage_.disconnect(self->pJournal_)) { // Idempotent: only the first worker to observe Drop counts the @@ -718,7 +718,7 @@ PeerImp::onTimer(error_code const& ec) return; } - if (largeSendq_++ >= Tuning::kSendqIntervals) + if (largeSendq_++ >= tuning::kSendqIntervals) { fail("Large send queue"); return; @@ -948,7 +948,7 @@ PeerImp::onReadMessage(error_code ec, std::size_t bytesTransferred) readBuffer_.commit(bytesTransferred); - auto hint = Tuning::kReadBufferBytes; + auto hint = tuning::kReadBufferBytes; while (readBuffer_.size() > 0) { @@ -980,7 +980,7 @@ PeerImp::onReadMessage(error_code ec, std::size_t bytesTransferred) // Timeout on writes only stream_.async_read_some( - readBuffer_.prepare(std::max(Tuning::kReadBufferBytes, hint)), + readBuffer_.prepare(std::max(tuning::kReadBufferBytes, hint)), bind_executor( strand_, [self = shared_from_this()](error_code const& ec, std::size_t bytesTransferred) { @@ -1056,7 +1056,7 @@ PeerImp::onMessageBegin( { auto const name = protocolMessageName(type); loadEvent_ = app_.getJobQueue().makeLoadEvent(JtPeer, name); - fee_ = {.fee = Resource::kFeeTrivialPeer, .context = name}; + fee_ = {.fee = resource::kFeeTrivialPeer, .context = name}; auto const category = TrafficCount::categorize(*m, static_cast(type), true); @@ -1100,12 +1100,12 @@ PeerImp::onMessage(std::shared_ptr const& m) if (s == 0) { - fee_.update(Resource::kFeeUselessData, "empty"); + fee_.update(resource::kFeeUselessData, "empty"); return; } if (s > 100) - fee_.update(Resource::kFeeModerateBurdenPeer, "oversize"); + fee_.update(resource::kFeeModerateBurdenPeer, "oversize"); app_.getJobQueue().addJob(JtManifest, "RcvManifests", [this, that = shared_from_this(), m]() { overlay_.onManifests(m, that); @@ -1118,7 +1118,7 @@ PeerImp::onMessage(std::shared_ptr const& m) if (m->type() == protocol::TMPing::ptPING) { // We have received a ping request, reply with a pong - fee_.update(Resource::kFeeModerateBurdenPeer, "ping request"); + fee_.update(resource::kFeeModerateBurdenPeer, "ping request"); m->set_type(protocol::TMPing::ptPONG); send(std::make_shared(*m, protocol::mtPING)); return; @@ -1159,7 +1159,7 @@ PeerImp::onMessage(std::shared_ptr const& m) // VFALCO NOTE I think we should drop the peer immediately if (!cluster()) { - fee_.update(Resource::kFeeUselessData, "unknown cluster"); + fee_.update(resource::kFeeUselessData, "unknown cluster"); return; } @@ -1186,15 +1186,15 @@ PeerImp::onMessage(std::shared_ptr const& m) int const loadSources = m->loadsources().size(); if (loadSources != 0) { - Resource::Gossip gossip; + resource::Gossip gossip; gossip.items.reserve(loadSources); for (int i = 0; i < m->loadsources().size(); ++i) { protocol::TMLoadSource const& node = m->loadsources(i); - Resource::Gossip::Item item; - item.address = beast::IP::Endpoint::fromString(node.name()); + resource::Gossip::Item item; + item.address = beast::ip::Endpoint::fromString(node.name()); item.balance = node.cost(); - if (item.address != beast::IP::Endpoint()) + if (item.address != beast::ip::Endpoint()) gossip.items.push_back(item); } overlay_.resourceManager().importConsumers(name(), gossip); @@ -1234,17 +1234,17 @@ PeerImp::onMessage(std::shared_ptr const& m) // implication for the protocol. if (m->endpoints_v2().size() >= 1024) { - fee_.update(Resource::kFeeUselessData, "endpoints too large"); + fee_.update(resource::kFeeUselessData, "endpoints too large"); return; } - std::vector endpoints; + std::vector endpoints; endpoints.reserve(m->endpoints_v2().size()); auto malformed = 0; for (auto const& tm : m->endpoints_v2()) { - auto result = beast::IP::Endpoint::fromStringChecked(tm.endpoint()); + auto result = beast::ip::Endpoint::fromStringChecked(tm.endpoint()); if (!result) { @@ -1256,7 +1256,7 @@ PeerImp::onMessage(std::shared_ptr const& m) // If hops == 0, this Endpoint describes the peer we are connected // to -- in that case, we take the remote address seen on the - // socket and store that in the IP::Endpoint. If this is the first + // socket and store that in the ip::Endpoint. If this is the first // time, then we'll verify that their listener can receive incoming // by performing a connectivity test. if hops > 0, then we just // take the address/port we were given @@ -1271,7 +1271,7 @@ PeerImp::onMessage(std::shared_ptr const& m) if (malformed > 0) { fee_.update( - Resource::kFeeInvalidData * malformed, + resource::kFeeInvalidData * malformed, std::to_string(malformed) + " malformed endpoints"); } @@ -1332,7 +1332,7 @@ PeerImp::handleTransaction( { JLOG(pJournal_.warn()) << "Ignoring Network relayed Tx containing " "tfInnerBatchTxn (handleTransaction)."; - fee_.update(Resource::kFeeModerateBurdenPeer, "inner batch txn"); + fee_.update(resource::kFeeModerateBurdenPeer, "inner batch txn"); return; } // LCOV_EXCL_STOP @@ -1345,7 +1345,7 @@ PeerImp::handleTransaction( // we have seen this transaction recently if (any(flags & HashRouterFlags::BAD)) { - fee_.update(Resource::kFeeUselessData, "known bad"); + fee_.update(resource::kFeeUselessData, "known bad"); JLOG(pJournal_.debug()) << "Ignoring known bad tx " << txID; } @@ -1419,7 +1419,7 @@ void PeerImp::onMessage(std::shared_ptr const& m) { auto badData = [&](std::string const& msg) { - fee_.update(Resource::kFeeInvalidData, "get_ledger " + msg); + fee_.update(resource::kFeeInvalidData, "get_ledger " + msg); JLOG(pJournal_.warn()) << "TMGetLedger: " << msg; }; auto const itype{m->itype()}; @@ -1499,7 +1499,7 @@ PeerImp::onMessage(std::shared_ptr const& m) // Verify query depth if (m->has_querydepth()) { - if (m->querydepth() > Tuning::kMaxQueryDepth || itype == protocol::liBASE) + if (m->querydepth() > tuning::kMaxQueryDepth || itype == protocol::liBASE) { badData("Invalid query depth"); return; @@ -1517,10 +1517,10 @@ PeerImp::onMessage(std::shared_ptr const& m) bool tooManyNodeIds = false; if (itype != protocol::liBASE) { - nodeIDs.reserve(std::min(m->nodeids_size(), Tuning::kSoftMaxReplyNodes)); + nodeIDs.reserve(std::min(m->nodeids_size(), tuning::kSoftMaxReplyNodes)); for (auto const& nodeId : m->nodeids()) { - if (nodeIDs.size() >= Tuning::kSoftMaxReplyNodes) + if (nodeIDs.size() >= tuning::kSoftMaxReplyNodes) { // The peer requested too many node IDs. Continue processing the received node // IDs up to the limit. If the request is legitimate then at least they will get @@ -1531,7 +1531,7 @@ PeerImp::onMessage(std::shared_ptr const& m) auto parsed = deserializeSHAMapNodeID(nodeId); if (!parsed) { - peer->charge(Resource::kFeeInvalidData, "TMGetLedger: Invalid node ID"); + peer->charge(resource::kFeeInvalidData, "TMGetLedger: Invalid node ID"); return; } nodeIDs.push_back(std::move(*parsed)); @@ -1543,7 +1543,7 @@ PeerImp::onMessage(std::shared_ptr const& m) // below is skipped for relay responses. if (tooManyNodeIds) { - peer->charge(Resource::kFeeModerateBurdenPeer, "TMGetLedger: too many node IDs"); + peer->charge(resource::kFeeModerateBurdenPeer, "TMGetLedger: too many node IDs"); // Truncate the request to what was actually parsed and charged for, so that if this // request ends up being relayed to another peer, we don't forward the oversized list. @@ -1553,7 +1553,7 @@ PeerImp::onMessage(std::shared_ptr const& m) } if (!m->has_requestcookie()) { - peer->charge(Resource::kFeeModerateBurdenPeer, "TMGetLedger: get ledger request"); + peer->charge(resource::kFeeModerateBurdenPeer, "TMGetLedger: get ledger request"); } peer->processLedgerRequest(m, std::move(nodeIDs)); @@ -1566,11 +1566,11 @@ PeerImp::onMessage(std::shared_ptr const& m) JLOG(pJournal_.trace()) << "onMessage, TMProofPathRequest"; if (!ledgerReplayEnabled_) { - fee_.update(Resource::kFeeMalformedRequest, "proof_path_request disabled"); + fee_.update(resource::kFeeMalformedRequest, "proof_path_request disabled"); return; } - fee_.update(Resource::kFeeModerateBurdenPeer, "received a proof path request"); + fee_.update(resource::kFeeModerateBurdenPeer, "received a proof path request"); std::weak_ptr const weak = shared_from_this(); app_.getJobQueue().addJob(JtReplayReq, "RcvProofPReq", [weak, m]() { if (auto peer = weak.lock()) @@ -1580,11 +1580,11 @@ PeerImp::onMessage(std::shared_ptr const& m) { if (reply.error() == protocol::TMReplyError::reBAD_REQUEST) { - peer->charge(Resource::kFeeMalformedRequest, "proof_path_request"); + peer->charge(resource::kFeeMalformedRequest, "proof_path_request"); } else { - peer->charge(Resource::kFeeRequestNoReply, "proof_path_request"); + peer->charge(resource::kFeeRequestNoReply, "proof_path_request"); } } else @@ -1600,13 +1600,13 @@ PeerImp::onMessage(std::shared_ptr const& m) { if (!ledgerReplayEnabled_) { - fee_.update(Resource::kFeeMalformedRequest, "proof_path_response disabled"); + fee_.update(resource::kFeeMalformedRequest, "proof_path_response disabled"); return; } if (!ledgerReplayMsgHandler_.processProofPathResponse(m)) { - fee_.update(Resource::kFeeInvalidData, "proof_path_response"); + fee_.update(resource::kFeeInvalidData, "proof_path_response"); } } @@ -1616,11 +1616,11 @@ PeerImp::onMessage(std::shared_ptr const& m) JLOG(pJournal_.trace()) << "onMessage, TMReplayDeltaRequest"; if (!ledgerReplayEnabled_) { - fee_.update(Resource::kFeeMalformedRequest, "replay_delta_request disabled"); + fee_.update(resource::kFeeMalformedRequest, "replay_delta_request disabled"); return; } - fee_.fee = Resource::kFeeModerateBurdenPeer; + fee_.fee = resource::kFeeModerateBurdenPeer; std::weak_ptr const weak = shared_from_this(); app_.getJobQueue().addJob(JtReplayReq, "RcvReplDReq", [weak, m]() { if (auto peer = weak.lock()) @@ -1630,11 +1630,11 @@ PeerImp::onMessage(std::shared_ptr const& m) { if (reply.error() == protocol::TMReplyError::reBAD_REQUEST) { - peer->charge(Resource::kFeeMalformedRequest, "replay_delta_request"); + peer->charge(resource::kFeeMalformedRequest, "replay_delta_request"); } else { - peer->charge(Resource::kFeeRequestNoReply, "replay_delta_request"); + peer->charge(resource::kFeeRequestNoReply, "replay_delta_request"); } } else @@ -1650,13 +1650,13 @@ PeerImp::onMessage(std::shared_ptr const& m) { if (!ledgerReplayEnabled_) { - fee_.update(Resource::kFeeMalformedRequest, "replay_delta_response disabled"); + fee_.update(resource::kFeeMalformedRequest, "replay_delta_response disabled"); return; } if (!ledgerReplayMsgHandler_.processReplayDeltaResponse(m)) { - fee_.update(Resource::kFeeInvalidData, "replay_delta_response"); + fee_.update(resource::kFeeInvalidData, "replay_delta_response"); } } @@ -1664,7 +1664,7 @@ void PeerImp::onMessage(std::shared_ptr const& m) { auto badData = [&](std::string const& msg) { - fee_.update(Resource::kFeeInvalidData, msg); + fee_.update(resource::kFeeInvalidData, msg); JLOG(pJournal_.warn()) << "TMLedgerData: " << msg; }; @@ -1715,7 +1715,7 @@ PeerImp::onMessage(std::shared_ptr const& m) } // Verify ledger nodes. - if (m->nodes_size() <= 0 || m->nodes_size() > Tuning::kHardMaxReplyNodes) + if (m->nodes_size() <= 0 || m->nodes_size() > tuning::kHardMaxReplyNodes) { badData("Invalid Ledger/TXset nodes " + std::to_string(m->nodes_size())); return; @@ -1875,14 +1875,14 @@ PeerImp::onMessage(std::shared_ptr const& m) (publicKeyType(makeSlice(set.nodepubkey())) != KeyType::Secp256k1)) { JLOG(pJournal_.warn()) << "Proposal: malformed"; - fee_.update(Resource::kFeeInvalidSignature, " signature can't be longer than 72 bytes"); + fee_.update(resource::kFeeInvalidSignature, " signature can't be longer than 72 bytes"); return; } if (!stringIsUInt256Sized(set.currenttxhash()) || !stringIsUInt256Sized(set.previousledger())) { JLOG(pJournal_.warn()) << "Proposal: malformed"; - fee_.update(Resource::kFeeMalformedRequest, "bad hashes"); + fee_.update(resource::kFeeMalformedRequest, "bad hashes"); return; } @@ -2164,13 +2164,13 @@ PeerImp::checkTracking(std::uint32_t seq1, std::uint32_t seq2) { std::uint32_t const diff = std::max(seq1, seq2) - std::min(seq1, seq2); - if (diff < Tuning::kConvergedLedgerLimit) + if (diff < tuning::kConvergedLedgerLimit) { // The peer's ledger sequence is close to the validation's tracking_ = Tracking::Converged; } - if ((diff > Tuning::kDivergedLedgerLimit) && (tracking_.load() != Tracking::Diverged)) + if ((diff > tuning::kDivergedLedgerLimit) && (tracking_.load() != Tracking::Diverged)) { // The peer's ledger sequence is way off the validation's std::scoped_lock const sl(recentLock_); @@ -2185,7 +2185,7 @@ PeerImp::onMessage(std::shared_ptr const& m) { if (!stringIsUInt256Sized(m->hash())) { - fee_.update(Resource::kFeeMalformedRequest, "bad hash"); + fee_.update(resource::kFeeMalformedRequest, "bad hash"); return; } @@ -2197,7 +2197,7 @@ PeerImp::onMessage(std::shared_ptr const& m) if (std::ranges::find(recentTxSets_, hash) != recentTxSets_.end()) { - fee_.update(Resource::kFeeUselessData, "duplicate (tsHAVE)"); + fee_.update(resource::kFeeUselessData, "duplicate (tsHAVE)"); return; } @@ -2218,7 +2218,7 @@ PeerImp::onValidatorListMessage( { JLOG(pJournal_.warn()) << "Ignored malformed " << messageType; // This shouldn't ever happen with a well-behaved peer - fee_.update(Resource::kFeeHeavyBurdenPeer, "no blobs"); + fee_.update(resource::kFeeHeavyBurdenPeer, "no blobs"); return; } @@ -2232,7 +2232,7 @@ PeerImp::onValidatorListMessage( // Charging this fee here won't hurt the peer in the normal // course of operation (ie. refresh every 5 minutes), but // will add up if the peer is misbehaving. - fee_.update(Resource::kFeeUselessData, "duplicate"); + fee_.update(resource::kFeeUselessData, "duplicate"); return; } @@ -2323,27 +2323,27 @@ PeerImp::onValidatorListMessage( // Charging this fee here won't hurt the peer in the normal // course of operation (ie. refresh every 5 minutes), but // will add up if the peer is misbehaving. - fee_.update(Resource::kFeeUselessData, " duplicate (same_sequence or known_sequence)"); + fee_.update(resource::kFeeUselessData, " duplicate (same_sequence or known_sequence)"); break; case ListDisposition::Stale: // There are very few good reasons for a peer to send an // old list, particularly more than once. - fee_.update(Resource::kFeeInvalidData, "expired"); + fee_.update(resource::kFeeInvalidData, "expired"); break; case ListDisposition::Untrusted: // Charging this fee here won't hurt the peer in the normal // course of operation (ie. refresh every 5 minutes), but // will add up if the peer is misbehaving. - fee_.update(Resource::kFeeUselessData, "untrusted"); + fee_.update(resource::kFeeUselessData, "untrusted"); break; case ListDisposition::Invalid: // This shouldn't ever happen with a well-behaved peer - fee_.update(Resource::kFeeInvalidSignature, "invalid list disposition"); + fee_.update(resource::kFeeInvalidSignature, "invalid list disposition"); break; case ListDisposition::UnsupportedVersion: // During a version transition, this may be legitimate. // If it happens frequently, that's probably bad. - fee_.update(Resource::kFeeInvalidData, "version"); + fee_.update(resource::kFeeInvalidData, "version"); break; // LCOV_EXCL_START default: @@ -2411,7 +2411,7 @@ PeerImp::onMessage(std::shared_ptr const& m) JLOG(pJournal_.debug()) << "ValidatorList: received validator list from peer using " << "protocol version " << to_string(protocol_) << " which shouldn't support this feature."; - fee_.update(Resource::kFeeUselessData, "unsupported peer"); + fee_.update(resource::kFeeUselessData, "unsupported peer"); return; } onValidatorListMessage( @@ -2421,7 +2421,7 @@ PeerImp::onMessage(std::shared_ptr const& m) { JLOG(pJournal_.warn()) << "ValidatorList: Exception, " << e.what(); using namespace std::string_literals; - fee_.update(Resource::kFeeInvalidData, e.what()); + fee_.update(resource::kFeeInvalidData, e.what()); } } @@ -2435,7 +2435,7 @@ PeerImp::onMessage(std::shared_ptr const& m JLOG(pJournal_.debug()) << "ValidatorListCollection: received validator list from peer " << "using protocol version " << to_string(protocol_) << " which shouldn't support this feature."; - fee_.update(Resource::kFeeUselessData, "unsupported peer"); + fee_.update(resource::kFeeUselessData, "unsupported peer"); return; } if (m->version() < 2) @@ -2444,7 +2444,7 @@ PeerImp::onMessage(std::shared_ptr const& m << "ValidatorListCollection: received invalid validator list " "version " << m->version() << " from peer using protocol version " << to_string(protocol_); - fee_.update(Resource::kFeeInvalidData, "wrong version"); + fee_.update(resource::kFeeInvalidData, "wrong version"); return; } onValidatorListMessage( @@ -2454,7 +2454,7 @@ PeerImp::onMessage(std::shared_ptr const& m { JLOG(pJournal_.warn()) << "ValidatorListCollection: Exception, " << e.what(); using namespace std::string_literals; - fee_.update(Resource::kFeeInvalidData, e.what()); + fee_.update(resource::kFeeInvalidData, e.what()); } } @@ -2464,7 +2464,7 @@ PeerImp::onMessage(std::shared_ptr const& m) if (m->validation().size() < 50) { JLOG(pJournal_.warn()) << "Validation: Too small"; - fee_.update(Resource::kFeeMalformedRequest, "too small"); + fee_.update(resource::kFeeMalformedRequest, "too small"); return; } @@ -2491,7 +2491,7 @@ PeerImp::onMessage(std::shared_ptr const& m) val->getSeenTime())) { JLOG(pJournal_.trace()) << "Validation: Not current"; - fee_.update(Resource::kFeeUselessData, "not current"); + fee_.update(resource::kFeeUselessData, "not current"); return; } @@ -2560,7 +2560,7 @@ PeerImp::onMessage(std::shared_ptr const& m) { JLOG(pJournal_.warn()) << "Exception processing validation: " << e.what(); using namespace std::string_literals; - fee_.update(Resource::kFeeMalformedRequest, e.what()); + fee_.update(resource::kFeeMalformedRequest, e.what()); } } @@ -2575,7 +2575,7 @@ PeerImp::onMessage(std::shared_ptr const& m) if (packet.query()) { // this is a query - if (sendQueue_.size() >= Tuning::kDropSendQueue) + if (sendQueue_.size() >= tuning::kDropSendQueue) { JLOG(pJournal_.debug()) << "GetObject: Large send queue"; return; @@ -2592,7 +2592,7 @@ PeerImp::onMessage(std::shared_ptr const& m) if (!txReduceRelayEnabled()) { JLOG(pJournal_.error()) << "TMGetObjectByHash: tx reduce-relay is disabled"; - fee_.update(Resource::kFeeMalformedRequest, "disabled"); + fee_.update(resource::kFeeMalformedRequest, "disabled"); return; } @@ -2609,19 +2609,19 @@ PeerImp::onMessage(std::shared_ptr const& m) if (!stringIsUInt256Sized(packet.ledgerhash())) { JLOG(pJournal_.debug()) << "GetObj: malformed ledgerhash from peer " << id_; - fee_.update(Resource::kFeeMalformedRequest, "get object ledger hash"); + fee_.update(resource::kFeeMalformedRequest, "get object ledger hash"); return; } } // Reject oversized requests before touching the NodeStore. // The legitimate upper bound (InboundLedger::getNeededHashes()) // is 8 hashes; anything beyond kHardMaxReplyNodes is non-conforming. - if (packet.objects_size() > Tuning::kHardMaxReplyNodes) + if (packet.objects_size() > tuning::kHardMaxReplyNodes) { JLOG(pJournal_.warn()) << "GetObj: oversized request from peer " << id_ << " (" << packet.objects_size() - << " > " << Tuning::kHardMaxReplyNodes << ")"; - fee_.update(Resource::kFeeInvalidData, "oversized get object request"); + << " > " << tuning::kHardMaxReplyNodes << ")"; + fee_.update(resource::kFeeInvalidData, "oversized get object request"); return; } @@ -2643,7 +2643,7 @@ PeerImp::onMessage(std::shared_ptr const& m) // back through the resource model so a misbehaving peer // is still accountable rather than silently dropped. JLOG(peer->pJournal_.warn()) << "GetObj: handler threw: " << e.what(); - peer->charge(Resource::kFeeRequestNoReply, "get object handler exception"); + peer->charge(resource::kFeeRequestNoReply, "get object handler exception"); } }); if (!queued) @@ -2660,7 +2660,7 @@ PeerImp::onMessage(std::shared_ptr const& m) // uncharged DoS window. Charge the base burden up-front (after // a successful enqueue); the per-lookup differential is added // in the worker. - fee_.update(Resource::kFeeModerateBurdenPeer, "received a get object by hash request"); + fee_.update(resource::kFeeModerateBurdenPeer, "received a get object by hash request"); } else { @@ -2739,7 +2739,7 @@ PeerImp::processGetObjectByHash(std::shared_ptr con // a peer cannot drive unbounded NodeStore lookups by sending // non-existent hashes. int const requested = packet.objects_size(); - int const iterLimit = std::min(requested, Tuning::kHardMaxReplyNodes); + int const iterLimit = std::min(requested, tuning::kHardMaxReplyNodes); for (int i = 0; i < iterLimit; ++i) { @@ -2770,7 +2770,7 @@ PeerImp::processGetObjectByHash(std::shared_ptr con // JobQueue worker thread. charge( // We pass `requested` directly here, instead of actual lookups done. Which could be - // std::min(packet.objects_size(), static_cast(Tuning::kHardMaxReplyNodes)); + // std::min(packet.objects_size(), static_cast(tuning::kHardMaxReplyNodes)); // Because we want to charge as per the request size, to discourage large requests. computeGetObjectByHashFee(requested, reply.objects_size()), "processed get object by hash request"); @@ -2785,7 +2785,7 @@ PeerImp::onMessage(std::shared_ptr const& m) if (!txReduceRelayEnabled()) { JLOG(pJournal_.error()) << "TMHaveTransactions: tx reduce-relay is disabled"; - fee_.update(Resource::kFeeMalformedRequest, "disabled"); + fee_.update(resource::kFeeMalformedRequest, "disabled"); return; } @@ -2810,7 +2810,7 @@ PeerImp::handleHaveTransactions(std::shared_ptr co if (!stringIsUInt256Sized(m->hashes(i))) { JLOG(pJournal_.error()) << "TMHaveTransactions with invalid hash size"; - fee_.update(Resource::kFeeMalformedRequest, "hash size"); + fee_.update(resource::kFeeMalformedRequest, "hash size"); return; } @@ -2848,7 +2848,7 @@ PeerImp::onMessage(std::shared_ptr const& m) if (!txReduceRelayEnabled()) { JLOG(pJournal_.error()) << "TMTransactions: tx reduce-relay is disabled"; - fee_.update(Resource::kFeeMalformedRequest, "disabled"); + fee_.update(resource::kFeeMalformedRequest, "disabled"); return; } @@ -2872,14 +2872,14 @@ PeerImp::onMessage(std::shared_ptr const& m) dispatch(strand_, [self = shared_from_this(), m]() { if (!m->has_validatorpubkey()) { - self->fee_.update(Resource::kFeeInvalidData, "squelch no pubkey"); + self->fee_.update(resource::kFeeInvalidData, "squelch no pubkey"); return; } auto validator = m->validatorpubkey(); auto const slice{makeSlice(validator)}; if (!publicKeyType(slice)) { - self->fee_.update(Resource::kFeeInvalidData, "squelch bad pubkey"); + self->fee_.update(resource::kFeeInvalidData, "squelch bad pubkey"); return; } PublicKey const key(slice); @@ -2899,7 +2899,7 @@ PeerImp::onMessage(std::shared_ptr const& m) } else if (!self->squelch_.addSquelch(key, std::chrono::seconds{duration})) { - self->fee_.update(Resource::kFeeInvalidData, "squelch duration"); + self->fee_.update(resource::kFeeInvalidData, "squelch duration"); } JLOG(self->pJournal_.debug()) @@ -2939,11 +2939,11 @@ PeerImp::doFetchPack(std::shared_ptr const& packet) if (!stringIsUInt256Sized(packet->ledgerhash())) { JLOG(pJournal_.warn()) << "FetchPack hash size malformed"; - fee_.update(Resource::kFeeMalformedRequest, "hash size"); + fee_.update(resource::kFeeMalformedRequest, "hash size"); return; } - fee_.fee = Resource::kFeeHeavyBurdenPeer; + fee_.fee = resource::kFeeHeavyBurdenPeer; uint256 const hash = uint256::fromRaw(packet->ledgerhash()); @@ -2966,7 +2966,7 @@ PeerImp::doTransactions(std::shared_ptr const& pack if (packet->objects_size() > reduce_relay::kMaxTxQueueSize) { JLOG(pJournal_.error()) << "doTransactions, invalid number of hashes"; - fee_.update(Resource::kFeeMalformedRequest, "too big"); + fee_.update(resource::kFeeMalformedRequest, "too big"); return; } @@ -2976,7 +2976,7 @@ PeerImp::doTransactions(std::shared_ptr const& pack if (!stringIsUInt256Sized(obj.hash())) { - fee_.update(Resource::kFeeMalformedRequest, "hash size"); + fee_.update(resource::kFeeMalformedRequest, "hash size"); return; } @@ -2988,7 +2988,7 @@ PeerImp::doTransactions(std::shared_ptr const& pack { JLOG(pJournal_.error()) << "doTransactions, transaction not found " << Slice(hash.data(), hash.size()); - fee_.update(Resource::kFeeMalformedRequest, "tx not found"); + fee_.update(resource::kFeeMalformedRequest, "tx not found"); return; } @@ -3039,7 +3039,7 @@ PeerImp::checkTransaction( { JLOG(pJournal_.warn()) << "Ignoring Network relayed Tx containing " "tfInnerBatchTxn (checkSignature)."; - charge(Resource::kFeeModerateBurdenPeer, "inner batch txn"); + charge(resource::kFeeModerateBurdenPeer, "inner batch txn"); return; } // LCOV_EXCL_STOP @@ -3051,7 +3051,7 @@ PeerImp::checkTransaction( JLOG(pJournal_.info()) << "Marking transaction " << stx->getTransactionID() << "as BAD because it's expired"; app_.getHashRouter().setFlags(stx->getTransactionID(), HashRouterFlags::BAD); - charge(Resource::kFeeUselessData, "expired tx"); + charge(resource::kFeeUselessData, "expired tx"); return; } @@ -3082,7 +3082,7 @@ PeerImp::checkTransaction( if (!batch) { JLOG(pJournal_.debug()) << "Charging for pseudo-transaction tx " << tx->getID(); - charge(Resource::kFeeUselessData, "pseudo tx"); + charge(resource::kFeeUselessData, "pseudo tx"); } return; @@ -3104,7 +3104,7 @@ PeerImp::checkTransaction( // Probably not necessary to set HashRouterFlags::BAD, but // doesn't hurt. app_.getHashRouter().setFlags(stx->getTransactionID(), HashRouterFlags::BAD); - charge(Resource::kFeeInvalidSignature, "check transaction signature failure"); + charge(resource::kFeeInvalidSignature, "check transaction signature failure"); return; } } @@ -3123,7 +3123,7 @@ PeerImp::checkTransaction( JLOG(pJournal_.debug()) << "Exception checking transaction: " << reason; } app_.getHashRouter().setFlags(stx->getTransactionID(), HashRouterFlags::BAD); - charge(Resource::kFeeInvalidSignature, "tx (impossible)"); + charge(resource::kFeeInvalidSignature, "tx (impossible)"); return; } @@ -3135,7 +3135,7 @@ PeerImp::checkTransaction( JLOG(pJournal_.warn()) << "Exception in " << __func__ << ": " << ex.what(); app_.getHashRouter().setFlags(stx->getTransactionID(), HashRouterFlags::BAD); using namespace std::string_literals; - charge(Resource::kFeeInvalidData, "tx "s + ex.what()); + charge(resource::kFeeInvalidData, "tx "s + ex.what()); } } @@ -3154,7 +3154,7 @@ PeerImp::checkPropose( { std::string const desc{"Proposal fails sig check"}; JLOG(pJournal_.warn()) << desc; - charge(Resource::kFeeInvalidSignature, desc); + charge(resource::kFeeInvalidSignature, desc); return; } @@ -3198,7 +3198,7 @@ PeerImp::checkValidation( { std::string const desc{"Validation forwarded by peer is invalid"}; JLOG(pJournal_.debug()) << desc; - charge(Resource::kFeeInvalidSignature, desc); + charge(resource::kFeeInvalidSignature, desc); return; } @@ -3223,7 +3223,7 @@ PeerImp::checkValidation( { JLOG(pJournal_.trace()) << "Exception processing validation: " << ex.what(); using namespace std::string_literals; - charge(Resource::kFeeMalformedRequest, "validation "s + ex.what()); + charge(resource::kFeeMalformedRequest, "validation "s + ex.what()); } } @@ -3380,7 +3380,7 @@ PeerImp::getLedger(std::shared_ptr const& m) { // Do not resource charge a peer responding to a relay if (!m->has_requestcookie()) - charge(Resource::kFeeMalformedRequest, "get_ledger ledgerSeq"); + charge(resource::kFeeMalformedRequest, "get_ledger ledgerSeq"); ledger.reset(); JLOG(pJournal_.warn()) << "getLedger: Invalid ledger sequence " << ledgerSeq; @@ -3462,7 +3462,7 @@ PeerImp::processLedgerRequest( } else { - if (sendQueue_.size() >= Tuning::kDropSendQueue) + if (sendQueue_.size() >= tuning::kDropSendQueue) { JLOG(pJournal_.debug()) << "processLedgerRequest: Large send queue"; return; @@ -3522,12 +3522,12 @@ PeerImp::processLedgerRequest( auto const queryDepth{m->has_querydepth() ? m->querydepth() : defaultDepth}; std::vector data; - data.reserve(Tuning::kSoftMaxReplyNodes); + data.reserve(tuning::kSoftMaxReplyNodes); auto const useLedgerNodeDepth = supportsFeature(ProtocolFeature::LedgerNodeDepth); for (auto const& nodeID : nodeIDs) { - if (ledgerData.nodes_size() >= Tuning::kSoftMaxReplyNodes) + if (ledgerData.nodes_size() >= tuning::kSoftMaxReplyNodes) break; data.clear(); @@ -3541,7 +3541,7 @@ PeerImp::processLedgerRequest( for (auto const& d : data) { - if (ledgerData.nodes_size() >= Tuning::kHardMaxReplyNodes) + if (ledgerData.nodes_size() >= tuning::kHardMaxReplyNodes) break; protocol::TMLedgerNode* node{ledgerData.add_nodes()}; @@ -3639,30 +3639,30 @@ PeerImp::processLedgerRequest( // // Misses are billed first against the billable budget because a node store // seek dominates a cache hit and because invalid hashes are ~100% miss by construction. -Resource::Charge +resource::Charge PeerImp::computeGetObjectByHashFee(int const requested, int const found) { - int const billable = std::max(0, requested - static_cast(Tuning::kFreeObjectsPerRequest)); + int const billable = std::max(0, requested - static_cast(tuning::kFreeObjectsPerRequest)); // Clamp `missed` so a future caller passing found > requested cannot // produce a negative value that flips the hits/misses split. int const missed = std::max(0, requested - found); int const billableMisses = std::min(missed, billable); int const billableHits = billable - billableMisses; - int sizeBand = Tuning::kCostBandSmall; - if (requested > Tuning::kBandMediumMax) + int sizeBand = tuning::kCostBandSmall; + if (requested > tuning::kBandMediumMax) { - sizeBand = Tuning::kCostBandLarge; + sizeBand = tuning::kCostBandLarge; } - else if (requested > Tuning::kBandSmallMax) + else if (requested > tuning::kBandSmallMax) { - sizeBand = Tuning::kCostBandMedium; + sizeBand = tuning::kCostBandMedium; } - int const dynamic = (billableHits * Tuning::kCostPerLookupHit) + - (billableMisses * Tuning::kCostPerLookupMiss) + sizeBand; + int const dynamic = (billableHits * tuning::kCostPerLookupHit) + + (billableMisses * tuning::kCostPerLookupMiss) + sizeBand; - return Resource::Charge(dynamic, "GetObject differential"); + return resource::Charge(dynamic, "GetObject differential"); } int diff --git a/src/xrpld/overlay/detail/PeerImp.h b/src/xrpld/overlay/detail/PeerImp.h index de90e60955..3fcfe6359a 100644 --- a/src/xrpld/overlay/detail/PeerImp.h +++ b/src/xrpld/overlay/detail/PeerImp.h @@ -97,7 +97,7 @@ private: // Updated at each stage of the connection process to reflect // the current conditions as closely as possible. - beast::IP::Endpoint const remoteAddress_; + beast::ip::Endpoint const remoteAddress_; // These are up here to prevent warnings about order of initializations // @@ -161,11 +161,11 @@ private: struct ChargeWithContext { - Resource::Charge fee = Resource::kFeeTrivialPeer; + resource::Charge fee = resource::kFeeTrivialPeer; std::string context{}; // NOLINT(readability-redundant-member-init) void - update(Resource::Charge f, std::string const& add) + update(resource::Charge f, std::string const& add) { XRPL_ASSERT(f >= fee, "xrpl::PeerImp::ChargeWithContext::update : fee increases"); fee = f; @@ -179,7 +179,7 @@ private: std::mutex mutable recentLock_; protocol::TMStatusChange lastStatus_; - Resource::Consumer usage_; + resource::Consumer usage_; ChargeWithContext fee_; // One-shot guard so concurrent JobQueue workers cannot double-count @@ -187,7 +187,7 @@ private: // post duplicate fail() calls) when several queued requests cross // kDropThreshold before the first fail() lands on the strand. std::atomic chargeDisconnectFired_{false}; - std::shared_ptr const slot_; + std::shared_ptr const slot_; boost::beast::multi_buffer readBuffer_; http_request_type request_; http_response_type response_; @@ -258,11 +258,11 @@ public: PeerImp( Application& app, id_t id, - std::shared_ptr const& slot, + std::shared_ptr const& slot, http_request_type&& request, PublicKey const& publicKey, ProtocolVersion protocol, - Resource::Consumer consumer, + resource::Consumer consumer, std::unique_ptr&& streamPtr, OverlayImpl& overlay); @@ -275,9 +275,9 @@ public: Application& app, std::unique_ptr&& streamPtr, Buffers const& buffers, - std::shared_ptr&& slot, + std::shared_ptr&& slot, http_response_type&& response, - Resource::Consumer usage, + resource::Consumer usage, PublicKey const& publicKey, ProtocolVersion protocol, id_t id, @@ -291,7 +291,7 @@ public: return pJournal_; } - std::shared_ptr const& + std::shared_ptr const& slot() { return slot_; @@ -339,16 +339,18 @@ public: void sendEndpoints(FwdIt first, FwdIt last) requires( - std::is_same_v::value_type, PeerFinder::Endpoint>); + std::is_same_v< // + typename std::iterator_traits::value_type, + peer_finder::Endpoint>); - beast::IP::Endpoint + beast::ip::Endpoint getRemoteAddress() const override { return remoteAddress_; } void - charge(Resource::Charge const& fee, std::string const& context) override; + charge(resource::Charge const& fee, std::string const& context) override; // // Identity @@ -697,7 +699,7 @@ protected: * * Dispatched from `onMessage(TMGetObjectByHash)` to the JobQueue * (`JtLedgerReq`) so synchronous NodeStore lookups do not block the - * peer's I/O strand. Caps iteration at `Tuning::kHardMaxReplyNodes` + * peer's I/O strand. Caps iteration at `tuning::kHardMaxReplyNodes` * regardless of hit/miss outcome and applies differential pricing * via `computeGetObjectByHashFee()` after the fetch loop completes. * @@ -711,25 +713,25 @@ protected: * request based on how much work was actually performed. * * The charge has three components on top of the base - * `Resource::kFeeModerateBurdenPeer`: + * `resource::kFeeModerateBurdenPeer`: * - per-hit lookup cost (cheap; usually served from cache) * - per-miss lookup cost (expensive node store seeks) * - request-size band surcharge (escalates abusive batch sizes) * - * The first `Tuning::kFreeObjectsPerRequest` objects are free so + * The first `tuning::kFreeObjectsPerRequest` objects are free so * that legitimate `InboundLedger::getNeededHashes()` traffic * (at most 8 objects) is unaffected. * * @param requested Number of objects requested by the message. This * value is used for request-size pricing and may - * exceed `Tuning::kHardMaxReplyNodes` when this + * exceed `tuning::kHardMaxReplyNodes` when this * helper is called directly, even though processing - * caps the iterations to `Tuning::kHardMaxReplyNodes`. + * caps the iterations to `tuning::kHardMaxReplyNodes`. * @param found Number of objects successfully returned in the * reply. - * @return A `Resource::Charge` whose cost reflects the work performed. + * @return A `resource::Charge` whose cost reflects the work performed. */ - static Resource::Charge + static resource::Charge computeGetObjectByHashFee(int const requested, int const found); /** @@ -740,9 +742,9 @@ protected: * full JobQueue handler. Production callers should never read this back — * the value is consumed by `charge()`/`disconnect()` internally. * - * @return The current `Resource::Charge` accumulated on `fee_`. + * @return The current `resource::Charge` accumulated on `fee_`. */ - Resource::Charge + resource::Charge currentFeeCharge() const { return fee_.fee; @@ -756,9 +758,9 @@ PeerImp::PeerImp( Application& app, std::unique_ptr&& streamPtr, Buffers const& buffers, - std::shared_ptr&& slot, + std::shared_ptr&& slot, http_response_type&& response, - Resource::Consumer usage, + resource::Consumer usage, PublicKey const& publicKey, ProtocolVersion protocol, id_t id, @@ -788,7 +790,7 @@ PeerImp::PeerImp( , creationTime_(clock_type::now()) , squelch_(app_.getJournal("Squelch")) , usage_(usage) - , fee_{.fee = Resource::kFeeTrivialPeer} + , fee_{.fee = resource::kFeeTrivialPeer} , slot_(std::move(slot)) , response_(std::move(response)) , headers_(response_) @@ -815,7 +817,8 @@ PeerImp::PeerImp( template void PeerImp::sendEndpoints(FwdIt first, FwdIt last) - requires(std::is_same_v::value_type, PeerFinder::Endpoint>) + requires( + std::is_same_v::value_type, peer_finder::Endpoint>) { protocol::TMEndpoints tm; diff --git a/src/xrpld/overlay/detail/Tuning.h b/src/xrpld/overlay/detail/Tuning.h index 5488fab07b..7561a4f385 100644 --- a/src/xrpld/overlay/detail/Tuning.h +++ b/src/xrpld/overlay/detail/Tuning.h @@ -5,7 +5,7 @@ #include #include -namespace xrpl::Tuning { +namespace xrpl::tuning { /** * How many ledgers off a server can be and we will @@ -74,7 +74,7 @@ constexpr std::size_t kReadBufferBytes = 16384; * while a hit is usually served from cache. On top of that, a size-band * surcharge kicks in for larger requests so an attacker who crams a * single message with thousands of hashes blows past - * `Resource::kDropThreshold` and gets disconnected. + * `resource::kDropThreshold` and gets disconnected. * * The numbers below are picked to keep three things true given * `kDropThreshold = 25000`: @@ -165,4 +165,4 @@ static constexpr auto kLegitHashesPerType = 4; static constexpr auto kBandSmallMax = kLegitHashesPerType * SHAMapInnerNode::kBranchFactor; static constexpr auto kBandMediumMax = kBandSmallMax * SHAMapInnerNode::kBranchFactor; -} // namespace xrpl::Tuning +} // namespace xrpl::tuning diff --git a/src/xrpld/overlay/make_Overlay.h b/src/xrpld/overlay/make_Overlay.h index a62d4b49de..c730a05c54 100644 --- a/src/xrpld/overlay/make_Overlay.h +++ b/src/xrpld/overlay/make_Overlay.h @@ -27,7 +27,7 @@ makeOverlay( Application& app, Overlay::Setup const& setup, ServerHandler& serverHandler, - Resource::Manager& resourceManager, + resource::Manager& resourceManager, Resolver& resolver, boost::asio::io_context& ioContext, BasicConfig const& config, diff --git a/src/xrpld/peerfinder/PeerfinderManager.h b/src/xrpld/peerfinder/PeerfinderManager.h index f96ea31943..5934e3bc4a 100644 --- a/src/xrpld/peerfinder/PeerfinderManager.h +++ b/src/xrpld/peerfinder/PeerfinderManager.h @@ -6,7 +6,7 @@ #include -namespace xrpl::PeerFinder { +namespace xrpl::peer_finder { Config makeConfig( @@ -16,4 +16,4 @@ makeConfig( int ipLimit, bool verifyEndpoints); -} // namespace xrpl::PeerFinder +} // namespace xrpl::peer_finder diff --git a/src/xrpld/peerfinder/detail/PeerfinderConfig.cpp b/src/xrpld/peerfinder/detail/PeerfinderConfig.cpp index a893b969e0..b222f6c077 100644 --- a/src/xrpld/peerfinder/detail/PeerfinderConfig.cpp +++ b/src/xrpld/peerfinder/detail/PeerfinderConfig.cpp @@ -5,7 +5,7 @@ #include -namespace xrpl::PeerFinder { +namespace xrpl::peer_finder { Config makeConfig( @@ -36,4 +36,4 @@ makeConfig( verifyEndpoints); } -} // namespace xrpl::PeerFinder +} // namespace xrpl::peer_finder diff --git a/src/xrpld/peerfinder/detail/StoreSqdb.h b/src/xrpld/peerfinder/detail/StoreSqdb.h index b0973a42b3..ce13d72c15 100644 --- a/src/xrpld/peerfinder/detail/StoreSqdb.h +++ b/src/xrpld/peerfinder/detail/StoreSqdb.h @@ -14,7 +14,7 @@ #include #include -namespace xrpl::PeerFinder { +namespace xrpl::peer_finder { /** * Database persistence for PeerFinder using SQLite @@ -50,7 +50,7 @@ public: std::size_t n(0); readPeerFinderDB(sqlDb_, [&](std::string const& s, int valence) { - beast::IP::Endpoint const endpoint(beast::IP::Endpoint::fromString(s)); + beast::ip::Endpoint const endpoint(beast::ip::Endpoint::fromString(s)); if (!isUnspecified(endpoint)) { @@ -90,4 +90,4 @@ private: } }; -} // namespace xrpl::PeerFinder +} // namespace xrpl::peer_finder diff --git a/src/xrpld/perflog/detail/PerfLogImp.h b/src/xrpld/perflog/detail/PerfLogImp.h index 14477512ff..7efdbe1b7f 100644 --- a/src/xrpld/perflog/detail/PerfLogImp.h +++ b/src/xrpld/perflog/detail/PerfLogImp.h @@ -109,7 +109,7 @@ class PerfLogImp : public PerfLog Application& app_; beast::Journal const j_; std::function const signalStop_; - Counters counters_{xrpl::RPC::getHandlerNames(), JobTypes::instance()}; + Counters counters_{xrpl::rpc::getHandlerNames(), JobTypes::instance()}; std::ofstream logFile_; std::thread thread_; std::mutex mutex_; diff --git a/src/xrpld/rpc/BookChanges.h b/src/xrpld/rpc/BookChanges.h index 3c10ece78f..16f7ea8e43 100644 --- a/src/xrpld/rpc/BookChanges.h +++ b/src/xrpld/rpc/BookChanges.h @@ -32,7 +32,7 @@ class Transaction; class TxMeta; class STTx; -namespace RPC { +namespace rpc { template json::Value @@ -233,5 +233,5 @@ computeBookChanges(std::shared_ptr const& lpAccepted) return jvObj; } -} // namespace RPC +} // namespace rpc } // namespace xrpl diff --git a/src/xrpld/rpc/CTID.h b/src/xrpld/rpc/CTID.h index 7566e0e143..71ded5834f 100644 --- a/src/xrpld/rpc/CTID.h +++ b/src/xrpld/rpc/CTID.h @@ -11,7 +11,7 @@ #include #include -namespace xrpl::RPC { +namespace xrpl::rpc { // CTID stands for Concise Transaction ID. // @@ -111,4 +111,4 @@ decodeCTID(T const ctid) noexcept return std::make_tuple(ledgerSeq, txnIndex, networkID); } -} // namespace xrpl::RPC +} // namespace xrpl::rpc diff --git a/src/xrpld/rpc/Context.h b/src/xrpld/rpc/Context.h index 81ba068d8f..e85bf6dfe6 100644 --- a/src/xrpld/rpc/Context.h +++ b/src/xrpld/rpc/Context.h @@ -18,7 +18,7 @@ class Application; class NetworkOPs; class LedgerMaster; -namespace RPC { +namespace rpc { /** * The context of information needed to call an RPC. @@ -27,10 +27,10 @@ struct Context { beast::Journal const j; Application& app; - Resource::Charge& loadType; + resource::Charge& loadType; NetworkOPs& netOps; LedgerMaster& ledgerMaster; - Resource::Consumer& consumer; + resource::Consumer& consumer; Role role; std::shared_ptr coro; InfoSub::pointer infoSub; @@ -59,5 +59,5 @@ struct GRPCContext : public Context RequestType params; }; -} // namespace RPC +} // namespace rpc } // namespace xrpl diff --git a/src/xrpld/rpc/DeliveredAmount.h b/src/xrpld/rpc/DeliveredAmount.h index dc635c6861..a7878070d6 100644 --- a/src/xrpld/rpc/DeliveredAmount.h +++ b/src/xrpld/rpc/DeliveredAmount.h @@ -17,7 +17,7 @@ class Transaction; class TxMeta; class STTx; -namespace RPC { +namespace rpc { struct JsonContext; @@ -41,23 +41,23 @@ insertDeliveredAmount( void insertDeliveredAmount( json::Value& meta, - RPC::JsonContext const&, + rpc::JsonContext const&, std::shared_ptr const&, TxMeta const&); void insertDeliveredAmount( json::Value& meta, - RPC::JsonContext const&, + rpc::JsonContext const&, std::shared_ptr const&, TxMeta const&); std::optional getDeliveredAmount( - RPC::Context const& context, + rpc::Context const& context, std::shared_ptr const& serializedTx, TxMeta const& transactionMeta, LedgerIndex const& ledgerIndex); /** @} */ -} // namespace RPC +} // namespace rpc } // namespace xrpl diff --git a/src/xrpld/rpc/GRPCHandlers.h b/src/xrpld/rpc/GRPCHandlers.h index 9dc7e0b13a..cabd55d53b 100644 --- a/src/xrpld/rpc/GRPCHandlers.h +++ b/src/xrpld/rpc/GRPCHandlers.h @@ -14,22 +14,22 @@ namespace xrpl { /* * These handlers are for gRPC. They each take in a protobuf message that is - * nested inside RPC::GRPCContext, where T is the request type + * nested inside rpc::GRPCContext, where T is the request type * The return value is the response type, as well as a status * If the status is not Status::OK (meaning an error occurred), then only * the status will be sent to the client, and the response will be omitted */ std::pair -doLedgerGrpc(RPC::GRPCContext& context); +doLedgerGrpc(rpc::GRPCContext& context); std::pair -doLedgerEntryGrpc(RPC::GRPCContext& context); +doLedgerEntryGrpc(rpc::GRPCContext& context); std::pair -doLedgerDataGrpc(RPC::GRPCContext& context); +doLedgerDataGrpc(rpc::GRPCContext& context); std::pair -doLedgerDiffGrpc(RPC::GRPCContext& context); +doLedgerDiffGrpc(rpc::GRPCContext& context); } // namespace xrpl diff --git a/src/xrpld/rpc/MPTokenIssuanceID.h b/src/xrpld/rpc/MPTokenIssuanceID.h index f56826bfb8..678fda0369 100644 --- a/src/xrpld/rpc/MPTokenIssuanceID.h +++ b/src/xrpld/rpc/MPTokenIssuanceID.h @@ -8,7 +8,7 @@ #include #include -namespace xrpl::RPC { +namespace xrpl::rpc { /** * Add a `mpt_issuance_id` field to the `meta` input/output parameter. @@ -32,4 +32,4 @@ insertMPTokenIssuanceID( TxMeta const& transactionMeta); /** @} */ -} // namespace xrpl::RPC +} // namespace xrpl::rpc diff --git a/src/xrpld/rpc/Output.h b/src/xrpld/rpc/Output.h index 30b5c090d7..f528efef56 100644 --- a/src/xrpld/rpc/Output.h +++ b/src/xrpld/rpc/Output.h @@ -5,7 +5,7 @@ #include #include -namespace xrpl::RPC { +namespace xrpl::rpc { using Output = std::function; @@ -15,4 +15,4 @@ stringOutput(std::string& s) return [&](boost::string_ref const& b) { s.append(b.data(), b.size()); }; } -} // namespace xrpl::RPC +} // namespace xrpl::rpc diff --git a/src/xrpld/rpc/RPCCall.h b/src/xrpld/rpc/RPCCall.h index a72b35e344..2fec78f93b 100644 --- a/src/xrpld/rpc/RPCCall.h +++ b/src/xrpld/rpc/RPCCall.h @@ -26,7 +26,7 @@ namespace xrpl { /** * Processes XRPL RPC calls. */ -namespace RPCCall { +namespace rpc_call { int fromCommandLine(Config const& config, std::vector const& vCmd, Logs& logs); @@ -47,7 +47,7 @@ fromNetwork( std::function callbackFuncP = std::function(), std::unordered_map headers = {}); -} // namespace RPCCall +} // namespace rpc_call json::Value rpcCmdToJson( diff --git a/src/xrpld/rpc/RPCHandler.h b/src/xrpld/rpc/RPCHandler.h index fcd0f54265..637a492943 100644 --- a/src/xrpld/rpc/RPCHandler.h +++ b/src/xrpld/rpc/RPCHandler.h @@ -8,7 +8,7 @@ #include -namespace xrpl::RPC { +namespace xrpl::rpc { struct JsonContext; @@ -16,9 +16,9 @@ struct JsonContext; * Execute an RPC command and store the results in a json::Value. */ Status -doCommand(RPC::JsonContext&, json::Value&); +doCommand(rpc::JsonContext&, json::Value&); Role roleRequired(unsigned int version, bool betaEnabled, std::string const& method); -} // namespace xrpl::RPC +} // namespace xrpl::rpc diff --git a/src/xrpld/rpc/Role.h b/src/xrpld/rpc/Role.h index 2c2ae6b781..48c89333bd 100644 --- a/src/xrpld/rpc/Role.h +++ b/src/xrpld/rpc/Role.h @@ -40,13 +40,13 @@ requestRole( Role const& required, Port const& port, json::Value const& params, - beast::IP::Endpoint const& remoteIp, + beast::ip::Endpoint const& remoteIp, std::string_view user); -Resource::Consumer +resource::Consumer requestInboundEndpoint( - Resource::Manager& manager, - beast::IP::Endpoint const& remoteAddress, + resource::Manager& manager, + beast::ip::Endpoint const& remoteAddress, Role const& role, std::string_view user, std::string_view forwardedFor); @@ -66,7 +66,7 @@ isUnlimited(Role const& role); */ bool ipAllowed( - beast::IP::Address const& remoteIp, + beast::ip::Address const& remoteIp, std::vector const& nets4, std::vector const& nets6); diff --git a/src/xrpld/rpc/ServerHandler.h b/src/xrpld/rpc/ServerHandler.h index 054bec9b5b..a09fc1c18a 100644 --- a/src/xrpld/rpc/ServerHandler.h +++ b/src/xrpld/rpc/ServerHandler.h @@ -80,7 +80,7 @@ private: using stream_type = boost::beast::ssl_stream; Application& app_; - Resource::Manager& resourceManager_; + resource::Manager& resourceManager_; beast::Journal journal_; NetworkOPs& networkOPs_; std::unique_ptr server_; @@ -109,7 +109,7 @@ private: boost::asio::io_context&, JobQueue&, NetworkOPs&, - Resource::Manager&, + resource::Manager&, CollectorManager& cm); public: @@ -120,7 +120,7 @@ public: boost::asio::io_context& ioContext, JobQueue& jobQueue, NetworkOPs& networkOPs, - Resource::Manager& resourceManager, + resource::Manager& resourceManager, CollectorManager& cm); ~ServerHandler(); @@ -196,7 +196,7 @@ private: processRequest( Port const& port, std::string const& request, - beast::IP::Endpoint const& remoteIPAddress, + beast::ip::Endpoint const& remoteIPAddress, Output const&, std::shared_ptr coro, std::string_view forwardedFor, @@ -215,7 +215,7 @@ makeServerHandler( boost::asio::io_context&, JobQueue&, NetworkOPs&, - Resource::Manager&, + resource::Manager&, CollectorManager& cm); } // namespace xrpl diff --git a/src/xrpld/rpc/Status.h b/src/xrpld/rpc/Status.h index dda1e89d31..e2716bd579 100644 --- a/src/xrpld/rpc/Status.h +++ b/src/xrpld/rpc/Status.h @@ -11,7 +11,7 @@ #include #include -namespace xrpl::RPC { +namespace xrpl::rpc { /** * Status represents the results of an operation that might fail. @@ -56,9 +56,11 @@ public: { } - /* Returns a representation of the integer status Code as a string. - If the Status is OK, the result is an empty string. - */ + /** + * If the Status is OK, the result is an empty string. + * + * @return a representation of the integer status Code as a string. + */ [[nodiscard]] std::string codeString() const; @@ -86,7 +88,7 @@ public: [[nodiscard]] TER toTER() const { - XRPL_ASSERT(type_ == Type::TER, "xrpl::RPC::Status::toTER : type is TER"); + XRPL_ASSERT(type_ == Type::TER, "xrpl::rpc::Status::toTER : type is TER"); return TER::fromInt(code_); } @@ -97,7 +99,8 @@ public: [[nodiscard]] ErrorCodeI toErrorCode() const { - XRPL_ASSERT(type_ == Type::ErrorCodeI, "xrpl::RPC::Status::toTER : type is error code"); + XRPL_ASSERT( + type_ == Type::ErrorCodeI, "xrpl::rpc::Status::toErrorCode : type is error code"); return ErrorCodeI(code_); } @@ -155,4 +158,4 @@ private: Strings messages_; }; -} // namespace xrpl::RPC +} // namespace xrpl::rpc diff --git a/src/xrpld/rpc/detail/DeliveredAmount.cpp b/src/xrpld/rpc/detail/DeliveredAmount.cpp index 8d8aac33bf..7b8c5e0623 100644 --- a/src/xrpld/rpc/detail/DeliveredAmount.cpp +++ b/src/xrpld/rpc/detail/DeliveredAmount.cpp @@ -16,7 +16,7 @@ #include #include -namespace xrpl::RPC { +namespace xrpl::rpc { /* GetLedgerIndex and GetCloseTime are lambdas that allow the close time and @@ -114,7 +114,7 @@ insertDeliveredAmount( template static std::optional getDeliveredAmount( - RPC::Context const& context, + rpc::Context const& context, std::shared_ptr const& serializedTx, TxMeta const& transactionMeta, GetLedgerIndex const& getLedgerIndex) @@ -133,7 +133,7 @@ getDeliveredAmount( std::optional getDeliveredAmount( - RPC::Context const& context, + rpc::Context const& context, std::shared_ptr const& serializedTx, TxMeta const& transactionMeta, LedgerIndex const& ledgerIndex) @@ -145,7 +145,7 @@ getDeliveredAmount( void insertDeliveredAmount( json::Value& meta, - RPC::JsonContext const& context, + rpc::JsonContext const& context, std::shared_ptr const& transaction, TxMeta const& transactionMeta) { @@ -155,7 +155,7 @@ insertDeliveredAmount( void insertDeliveredAmount( json::Value& meta, - RPC::JsonContext const& context, + rpc::JsonContext const& context, std::shared_ptr const& transaction, TxMeta const& transactionMeta) { @@ -178,4 +178,4 @@ insertDeliveredAmount( } } -} // namespace xrpl::RPC +} // namespace xrpl::rpc diff --git a/src/xrpld/rpc/detail/Handler.cpp b/src/xrpld/rpc/detail/Handler.cpp index 4f5ce34c1f..326af4f4ee 100644 --- a/src/xrpld/rpc/detail/Handler.cpp +++ b/src/xrpld/rpc/detail/Handler.cpp @@ -18,7 +18,7 @@ #include #include -namespace xrpl::RPC { +namespace xrpl::rpc { namespace { /** @@ -33,8 +33,8 @@ byRef(Function const& f) if (result.type() != json::ValueType::Object) { // LCOV_EXCL_START - UNREACHABLE("xrpl::RPC::byRef : result is object"); - result = RPC::makeObjectValue(result); + UNREACHABLE("xrpl::rpc::byRef : result is object"); + result = rpc::makeObjectValue(result); // LCOV_EXCL_STOP } @@ -49,7 +49,7 @@ handle(JsonContext& context, Object& object) XRPL_ASSERT( context.apiVersion >= HandlerImpl::minApiVer && context.apiVersion <= HandlerImpl::maxApiVer, - "xrpl::RPC::handle : valid API version"); + "xrpl::rpc::handle : valid API version"); HandlerImpl handler(context); auto status = handler.check(); @@ -382,10 +382,10 @@ private: unsigned minVer, unsigned maxVer) { - XRPL_ASSERT(minVer <= maxVer, "xrpl::RPC::HandlerTable : valid API version range"); + XRPL_ASSERT(minVer <= maxVer, "xrpl::rpc::HandlerTable : valid API version range"); XRPL_ASSERT( - maxVer <= RPC::kApiMaximumValidVersion, - "xrpl::RPC::HandlerTable : valid max API version"); + maxVer <= rpc::kApiMaximumValidVersion, + "xrpl::rpc::HandlerTable : valid max API version"); return std::any_of( range.first, @@ -427,8 +427,8 @@ public: [[nodiscard]] Handler const* getHandler(unsigned version, bool betaEnabled, std::string const& name) const { - if (version < RPC::kApiMinimumSupportedVersion || - version > (betaEnabled ? RPC::kApiBetaVersion : RPC::kApiMaximumSupportedVersion)) + if (version < rpc::kApiMinimumSupportedVersion || + version > (betaEnabled ? rpc::kApiBetaVersion : rpc::kApiMaximumSupportedVersion)) return nullptr; auto const range = table_.equal_range(name); @@ -457,8 +457,8 @@ private: addHandler() { static_assert(HandlerImpl::minApiVer <= HandlerImpl::maxApiVer); - static_assert(HandlerImpl::maxApiVer <= RPC::kApiMaximumValidVersion); - static_assert(RPC::kApiMinimumSupportedVersion <= HandlerImpl::minApiVer); + static_assert(HandlerImpl::maxApiVer <= rpc::kApiMaximumValidVersion); + static_assert(rpc::kApiMinimumSupportedVersion <= HandlerImpl::minApiVer); if (overlappingApiVersion( table_.equal_range(HandlerImpl::name), @@ -488,4 +488,4 @@ getHandlerNames() return HandlerTable::instance().getHandlerNames(); } -} // namespace xrpl::RPC +} // namespace xrpl::rpc diff --git a/src/xrpld/rpc/detail/Handler.h b/src/xrpld/rpc/detail/Handler.h index 37259c8648..7342c5fcbf 100644 --- a/src/xrpld/rpc/detail/Handler.h +++ b/src/xrpld/rpc/detail/Handler.h @@ -20,7 +20,7 @@ namespace json { class Object; } // namespace json -namespace xrpl::RPC { +namespace xrpl::rpc { // Under what condition can we call this RPC? enum class Condition { @@ -38,7 +38,7 @@ struct Handler char const* name; Method valueMethod; Role role; - RPC::Condition condition; + rpc::Condition condition; unsigned minApiVer = kApiMinimumSupportedVersion; unsigned maxApiVer = kApiMaximumValidVersion; @@ -92,7 +92,7 @@ conditionMet(Condition conditionRequired, T& context) if (!context.app.config().standalone() && conditionRequired != Condition::NoCondition) { - if (context.ledgerMaster.getValidatedLedgerAge() > Tuning::kMaxValidatedLedgerAge) + if (context.ledgerMaster.getValidatedLedgerAge() > tuning::kMaxValidatedLedgerAge) { if (context.apiVersion == 1) return RpcNoCurrent; @@ -122,4 +122,4 @@ conditionMet(Condition conditionRequired, T& context) return RpcSuccess; } -} // namespace xrpl::RPC +} // namespace xrpl::rpc diff --git a/src/xrpld/rpc/detail/LegacyPathFind.cpp b/src/xrpld/rpc/detail/LegacyPathFind.cpp index 0bfa19a1f4..837d98084e 100644 --- a/src/xrpld/rpc/detail/LegacyPathFind.cpp +++ b/src/xrpld/rpc/detail/LegacyPathFind.cpp @@ -9,7 +9,7 @@ #include -namespace xrpl::RPC { +namespace xrpl::rpc { LegacyPathFind::LegacyPathFind(bool isAdmin, Application& app) { @@ -21,13 +21,13 @@ LegacyPathFind::LegacyPathFind(bool isAdmin, Application& app) } auto const& jobCount = app.getJobQueue().getJobCountGE(JtClient); - if (jobCount > Tuning::kMaxPathfindJobCount || app.getFeeTrack().isLoadedLocal()) + if (jobCount > tuning::kMaxPathfindJobCount || app.getFeeTrack().isLoadedLocal()) return; while (true) { int prevVal = inProgress.load(); - if (prevVal >= Tuning::kMaxPathfindsInProgress) + if (prevVal >= tuning::kMaxPathfindsInProgress) return; if (inProgress.compare_exchange_strong( @@ -47,4 +47,4 @@ LegacyPathFind::~LegacyPathFind() std::atomic LegacyPathFind::inProgress(0); -} // namespace xrpl::RPC +} // namespace xrpl::rpc diff --git a/src/xrpld/rpc/detail/LegacyPathFind.h b/src/xrpld/rpc/detail/LegacyPathFind.h index 226191848f..30e176f245 100644 --- a/src/xrpld/rpc/detail/LegacyPathFind.h +++ b/src/xrpld/rpc/detail/LegacyPathFind.h @@ -6,7 +6,7 @@ namespace xrpl { class Application; -namespace RPC { +namespace rpc { class LegacyPathFind { @@ -26,5 +26,5 @@ private: bool isOk_{false}; }; -} // namespace RPC +} // namespace rpc } // namespace xrpl diff --git a/src/xrpld/rpc/detail/MPTokenIssuanceID.cpp b/src/xrpld/rpc/detail/MPTokenIssuanceID.cpp index e34980aee2..4f57bab9ab 100644 --- a/src/xrpld/rpc/detail/MPTokenIssuanceID.cpp +++ b/src/xrpld/rpc/detail/MPTokenIssuanceID.cpp @@ -16,7 +16,7 @@ #include #include -namespace xrpl::RPC { +namespace xrpl::rpc { bool canHaveMPTokenIssuanceID( @@ -67,4 +67,4 @@ insertMPTokenIssuanceID( response[jss::mpt_issuance_id] = to_string(result.value()); } -} // namespace xrpl::RPC +} // namespace xrpl::rpc diff --git a/src/xrpld/rpc/detail/PathRequest.cpp b/src/xrpld/rpc/detail/PathRequest.cpp index 3c09917dad..fb132199bc 100644 --- a/src/xrpld/rpc/detail/PathRequest.cpp +++ b/src/xrpld/rpc/detail/PathRequest.cpp @@ -73,7 +73,7 @@ PathRequest::PathRequest( PathRequest::PathRequest( Application& app, std::function completion, - Resource::Consumer& consumer, + resource::Consumer& consumer, int id, PathRequestManager& owner, beast::Journal journal) @@ -344,7 +344,7 @@ PathRequest::parseJson(json::Value const& jvParams) { json::Value const& jvSrcCurrencies = jvParams[jss::source_currencies]; if (!jvSrcCurrencies.isArray() || jvSrcCurrencies.size() == 0 || - jvSrcCurrencies.size() > RPC::Tuning::kMaxSrcCur) + jvSrcCurrencies.size() > rpc::tuning::kMaxSrcCur) { jvStatus_ = rpcError(RpcSrcCurMalformed); return PFR_PJ_INVALID; @@ -556,7 +556,7 @@ PathRequest::findPaths( [&](TAsset const& a) { if (!sameAccount || a != saDstAmount_.asset()) { - if (sourceAssets.size() >= RPC::Tuning::kMaxAutoSrcCur) + if (sourceAssets.size() >= rpc::tuning::kMaxAutoSrcCur) return false; if constexpr (std::is_same_v) { diff --git a/src/xrpld/rpc/detail/PathRequest.h b/src/xrpld/rpc/detail/PathRequest.h index d40d9c82d6..f56b3d0652 100644 --- a/src/xrpld/rpc/detail/PathRequest.h +++ b/src/xrpld/rpc/detail/PathRequest.h @@ -64,7 +64,7 @@ public: PathRequest( Application& app, std::function completion, - Resource::Consumer& consumer, + resource::Consumer& consumer, int id, PathRequestManager&, beast::Journal journal); @@ -137,7 +137,7 @@ private: std::weak_ptr wpSubscriber_; // Who this request came from std::function fCompletion_; - Resource::Consumer& consumer_; // Charge according to source currencies + resource::Consumer& consumer_; // Charge according to source currencies json::Value jvId_; json::Value jvStatus_; // Last result diff --git a/src/xrpld/rpc/detail/PathRequestManager.cpp b/src/xrpld/rpc/detail/PathRequestManager.cpp index 4953634181..117bfbda1e 100644 --- a/src/xrpld/rpc/detail/PathRequestManager.cpp +++ b/src/xrpld/rpc/detail/PathRequestManager.cpp @@ -254,7 +254,7 @@ json::Value PathRequestManager::makeLegacyPathRequest( PathRequest::pointer& req, std::function completion, - Resource::Consumer& consumer, + resource::Consumer& consumer, std::shared_ptr const& inLedger, json::Value const& request) { @@ -285,7 +285,7 @@ PathRequestManager::makeLegacyPathRequest( json::Value PathRequestManager::doLegacyPathRequest( - Resource::Consumer& consumer, + resource::Consumer& consumer, std::shared_ptr const& inLedger, json::Value const& request) { diff --git a/src/xrpld/rpc/detail/PathRequestManager.h b/src/xrpld/rpc/detail/PathRequestManager.h index f6eb80d291..29a80e66c0 100644 --- a/src/xrpld/rpc/detail/PathRequestManager.h +++ b/src/xrpld/rpc/detail/PathRequestManager.h @@ -65,7 +65,7 @@ public: makeLegacyPathRequest( PathRequest::pointer& req, std::function completion, - Resource::Consumer& consumer, + resource::Consumer& consumer, std::shared_ptr const& inLedger, json::Value const& request); @@ -73,7 +73,7 @@ public: // with the ledger specified by the caller json::Value doLegacyPathRequest( - Resource::Consumer& consumer, + resource::Consumer& consumer, std::shared_ptr const& inLedger, json::Value const& request); diff --git a/src/xrpld/rpc/detail/RPCCall.cpp b/src/xrpld/rpc/detail/RPCCall.cpp index b5d5c680cd..a752858527 100644 --- a/src/xrpld/rpc/detail/RPCCall.cpp +++ b/src/xrpld/rpc/detail/RPCCall.cpp @@ -74,7 +74,7 @@ createHTTPPost( // CHECKME this uses a different version than the replies below use. Is // this by design or an accident or should it be using - // BuildInfo::getFullVersionString () as well? + // build_info::getFullVersionString () as well? s << "POST " << (strPath.empty() ? "/" : strPath) << " HTTP/1.0\r\n" << "User-Agent: " << systemName() << "-json-rpc/v1\r\n" @@ -149,7 +149,7 @@ private: return jvResult; } - return RPC::makeParamError( + return rpc::makeParamError( std::string("Invalid currency/issuer '") + strCurrencyIssuer + "'"); } @@ -355,7 +355,7 @@ private: } catch (std::exception const&) { - return RPC::invalidFieldError(jss::limit); + return rpc::invalidFieldError(jss::limit); } } @@ -369,7 +369,7 @@ private: } catch (std::exception const&) { - return RPC::invalidFieldError(jss::proof); + return rpc::invalidFieldError(jss::proof); } } @@ -1182,7 +1182,7 @@ private: std::string param = jvParams[index++].asString(); if (param.empty()) - return RPC::makeParamError("Invalid first parameter"); + return rpc::makeParamError("Invalid first parameter"); if (param[0] != 'r') { @@ -1196,7 +1196,7 @@ private: } if (size <= index) - return RPC::makeParamError("Invalid hotwallet"); + return rpc::makeParamError("Invalid hotwallet"); param = jvParams[index++].asString(); } @@ -1726,7 +1726,7 @@ rpcClient( { boost::asio::io_context isService; - RPCCall::fromNetwork( + rpc_call::fromNetwork( isService, setup.client.ip, setup.client.port, @@ -1813,12 +1813,12 @@ rpcClient( //------------------------------------------------------------------------------ -namespace RPCCall { +namespace rpc_call { int fromCommandLine(Config const& config, std::vector const& vCmd, Logs& logs) { - auto const result = rpcClient(vCmd, config, logs, RPC::kApiCommandLineVersion); + auto const result = rpcClient(vCmd, config, logs, rpc::kApiCommandLineVersion); std::cout << result.second.toStyledString(); @@ -1883,6 +1883,6 @@ fromNetwork( j); } -} // namespace RPCCall +} // namespace rpc_call } // namespace xrpl diff --git a/src/xrpld/rpc/detail/RPCHandler.cpp b/src/xrpld/rpc/detail/RPCHandler.cpp index 6f46aed62d..96d6bf72d7 100644 --- a/src/xrpld/rpc/detail/RPCHandler.cpp +++ b/src/xrpld/rpc/detail/RPCHandler.cpp @@ -24,7 +24,7 @@ #include #include -namespace xrpl::RPC { +namespace xrpl::rpc { namespace { @@ -114,7 +114,7 @@ fillHandler(JsonContext& context, Handler const*& result) { // Count all jobs at jtCLIENT priority or higher. int const jobCount = context.app.getJobQueue().getJobCountGE(JtClient); - if (jobCount > Tuning::kMaxJobQueueClients) + if (jobCount > tuning::kMaxJobQueueClients) { JLOG(context.j.debug()) << "Too busy for command: " << jobCount; return RpcTooBusy; @@ -179,8 +179,8 @@ callMethod(JsonContext& context, Method method, std::string const& name, Object& perfLog.rpcError(name, curId); JLOG(context.j.info()) << "Caught throw: " << e.what(); - if (context.loadType == Resource::kFeeReferenceRpc) - context.loadType = Resource::kFeeExceptionRpc; + if (context.loadType == resource::kFeeReferenceRpc) + context.loadType = resource::kFeeExceptionRpc; injectError(RpcInternal, result); return RpcInternal; @@ -190,7 +190,7 @@ callMethod(JsonContext& context, Method method, std::string const& name, Object& } // namespace Status -doCommand(RPC::JsonContext& context, json::Value& result) +doCommand(rpc::JsonContext& context, json::Value& result) { Handler const* handler = nullptr; if (auto error = fillHandler(context, handler)) @@ -226,7 +226,7 @@ doCommand(RPC::JsonContext& context, json::Value& result) Role roleRequired(unsigned int version, bool betaEnabled, std::string const& method) { - auto handler = RPC::getHandler(version, betaEnabled, method); + auto handler = rpc::getHandler(version, betaEnabled, method); if (handler == nullptr) return Role::FORBID; @@ -234,4 +234,4 @@ roleRequired(unsigned int version, bool betaEnabled, std::string const& method) return handler->role; } -} // namespace xrpl::RPC +} // namespace xrpl::rpc diff --git a/src/xrpld/rpc/detail/RPCHelpers.cpp b/src/xrpld/rpc/detail/RPCHelpers.cpp index 1764375812..4fa0fab6f7 100644 --- a/src/xrpld/rpc/detail/RPCHelpers.cpp +++ b/src/xrpld/rpc/detail/RPCHelpers.cpp @@ -42,7 +42,7 @@ #include #include -namespace xrpl::RPC { +namespace xrpl::rpc { std::uint64_t getStartHint(SLE::const_ref sle, AccountID const& accountID) @@ -116,7 +116,7 @@ parseAccountIds(json::Value const& jvArray) } std::optional -readLimitField(unsigned int& limit, Tuning::LimitRange const& range, JsonContext const& context) +readLimitField(unsigned int& limit, tuning::LimitRange const& range, JsonContext const& context) { limit = range.rDefault; if (!context.params.isMember(jss::limit) || context.params[jss::limit].isNull()) @@ -124,11 +124,11 @@ readLimitField(unsigned int& limit, Tuning::LimitRange const& range, JsonContext auto const& jvLimit = context.params[jss::limit]; if (!jvLimit.isUInt() && (!jvLimit.isInt() || jvLimit.asInt() < 0)) - return RPC::expectedFieldError(jss::limit, "unsigned integer"); + return rpc::expectedFieldError(jss::limit, "unsigned integer"); limit = jvLimit.asUInt(); if (limit == 0) - return RPC::invalidFieldError(jss::limit); + return rpc::invalidFieldError(jss::limit); if (!isUnlimited(context.role)) limit = std::max(range.rmin, std::min(range.rmax, limit)); @@ -184,7 +184,7 @@ getSeedFromRPC(json::Value const& params, json::Value& error) if (count != 1) { - error = RPC::makeParamError( + error = rpc::makeParamError( "Exactly one of the following must be specified: " + std::string(jss::passphrase) + ", " + std::string(jss::seed) + " or " + std::string(jss::seed_hex)); return std::nullopt; @@ -194,7 +194,7 @@ getSeedFromRPC(json::Value const& params, json::Value& error) auto const& param = params[seedType->first]; if (!param.isString()) { - error = RPC::expectedFieldError(seedType->first, "string"); + error = rpc::expectedFieldError(seedType->first, "string"); return std::nullopt; } @@ -232,13 +232,13 @@ keypairForSignature(json::Value const& params, json::Value& error, unsigned int if (count == 0 || secretType == nullptr) { - error = RPC::missingFieldError(jss::secret); + error = rpc::missingFieldError(jss::secret); return {}; } if (count > 1) { - error = RPC::makeParamError( + error = rpc::makeParamError( "Exactly one of the following must be specified: " + std::string(jss::passphrase) + ", " + std::string(jss::secret) + ", " + std::string(jss::seed) + " or " + std::string(jss::seed_hex)); @@ -252,7 +252,7 @@ keypairForSignature(json::Value const& params, json::Value& error, unsigned int { if (!params[jss::key_type].isString()) { - error = RPC::expectedFieldError(jss::key_type, "string"); + error = rpc::expectedFieldError(jss::key_type, "string"); return {}; } @@ -262,11 +262,11 @@ keypairForSignature(json::Value const& params, json::Value& error, unsigned int { if (apiVersion > 1u) { - error = RPC::makeError(RpcBadKeyType); + error = rpc::makeError(RpcBadKeyType); } else { - error = RPC::invalidFieldError(jss::key_type); + error = rpc::invalidFieldError(jss::key_type); } return {}; } @@ -275,7 +275,7 @@ keypairForSignature(json::Value const& params, json::Value& error, unsigned int // https://developercommunity.visualstudio.com/t/assigning-constexpr-char--to-static-cha/10021357?entry=problem) if (strcmp(secretType, jss::secret.cStr()) == 0) { - error = RPC::makeParamError( + error = rpc::makeParamError( "The secret field is not allowed if " + std::string(jss::key_type) + " is used."); return {}; } @@ -288,7 +288,7 @@ keypairForSignature(json::Value const& params, json::Value& error, unsigned int // https://developercommunity.visualstudio.com/t/assigning-constexpr-char--to-static-cha/10021357?entry=problem) if (strcmp(secretType, jss::seed_hex.cStr()) != 0) { - seed = RPC::parseXrplLibSeed(params[secretType]); + seed = rpc::parseXrplLibSeed(params[secretType]); if (seed) { @@ -296,7 +296,7 @@ keypairForSignature(json::Value const& params, json::Value& error, unsigned int // requested another key type, return an error. if (keyType.value_or(KeyType::Ed25519) != KeyType::Ed25519) { - error = RPC::makeError(RpcBadSeed, "Specified seed is for an Ed25519 wallet."); + error = rpc::makeError(RpcBadSeed, "Specified seed is for an Ed25519 wallet."); return {}; } @@ -317,7 +317,7 @@ keypairForSignature(json::Value const& params, json::Value& error, unsigned int { if (!params[jss::secret].isString()) { - error = RPC::expectedFieldError(jss::secret, "string"); + error = rpc::expectedFieldError(jss::secret, "string"); return {}; } @@ -329,7 +329,7 @@ keypairForSignature(json::Value const& params, json::Value& error, unsigned int { if (!containsError(error)) { - error = RPC::makeError(RpcBadSeed, RPC::invalidFieldMessage(secretType)); + error = rpc::makeError(RpcBadSeed, rpc::invalidFieldMessage(secretType)); } return {}; @@ -341,10 +341,10 @@ keypairForSignature(json::Value const& params, json::Value& error, unsigned int return generateKeyPair(*keyType, *seed); } -std::pair +std::pair chooseLedgerEntryType(json::Value const& params) { - std::pair result{RPC::Status::kOK, ltANY}; + std::pair result{rpc::Status::kOK, ltANY}; if (params.isMember(jss::type)) { static constexpr auto kTypes = @@ -363,10 +363,10 @@ chooseLedgerEntryType(json::Value const& params) auto const& p = params[jss::type]; if (!p.isString()) { - result.first = RPC::Status{RpcInvalidParams, "Invalid field 'type', not string."}; + result.first = rpc::Status{RpcInvalidParams, "Invalid field 'type', not string."}; XRPL_ASSERT( - result.first.type() == RPC::Status::Type::ErrorCodeI, - "xrpl::RPC::chooseLedgerEntryType : first valid result type"); + result.first.type() == rpc::Status::Type::ErrorCodeI, + "xrpl::rpc::chooseLedgerEntryType : first valid result type"); return result; } @@ -379,10 +379,10 @@ chooseLedgerEntryType(json::Value const& params) }); if (iter == kTypes.end()) { - result.first = RPC::Status{RpcInvalidParams, "Invalid field 'type'."}; + result.first = rpc::Status{RpcInvalidParams, "Invalid field 'type'."}; XRPL_ASSERT( - result.first.type() == RPC::Status::Type::ErrorCodeI, - "xrpl::RPC::chooseLedgerEntryType : second valid result " + result.first.type() == rpc::Status::Type::ErrorCodeI, + "xrpl::rpc::chooseLedgerEntryType : second valid result " "type"); return result; } @@ -466,4 +466,4 @@ parseSubUnsubJson( return RpcSuccess; } -} // namespace xrpl::RPC +} // namespace xrpl::rpc diff --git a/src/xrpld/rpc/detail/RPCHelpers.h b/src/xrpld/rpc/detail/RPCHelpers.h index 881b758487..24c06021c0 100644 --- a/src/xrpld/rpc/detail/RPCHelpers.h +++ b/src/xrpld/rpc/detail/RPCHelpers.h @@ -27,7 +27,7 @@ namespace xrpl { class ReadView; -namespace RPC { +namespace rpc { struct JsonContext; @@ -85,7 +85,7 @@ parseAccountIds(json::Value const& jvArray); * std::nullopt on success. */ std::optional -readLimitField(unsigned int& limit, Tuning::LimitRange const& range, JsonContext const& context); +readLimitField(unsigned int& limit, tuning::LimitRange const& range, JsonContext const& context); /** * @brief Extracts a Seed from RPC parameters. @@ -123,7 +123,7 @@ parseXrplLibSeed(json::Value const& params); * @param params The JSON value containing RPC parameters. * @return A pair consisting of the RPC status and the chosen LedgerEntryType. */ -std::pair +std::pair chooseLedgerEntryType(json::Value const& params); /** @@ -172,6 +172,6 @@ parseSubUnsubJson( json::StaticString const& name, beast::Journal j); -} // namespace RPC +} // namespace rpc } // namespace xrpl diff --git a/src/xrpld/rpc/detail/RPCLedgerHelpers.cpp b/src/xrpld/rpc/detail/RPCLedgerHelpers.cpp index 6843c34b19..52e68e87f1 100644 --- a/src/xrpld/rpc/detail/RPCLedgerHelpers.cpp +++ b/src/xrpld/rpc/detail/RPCLedgerHelpers.cpp @@ -30,7 +30,7 @@ #include #include -namespace xrpl::RPC { +namespace xrpl::rpc { namespace { @@ -40,7 +40,7 @@ isValidatedOld(LedgerMaster& ledgerMaster, bool standalone) if (standalone) return false; - return ledgerMaster.getValidatedLedgerAge() > Tuning::kMaxValidatedLedgerAge; + return ledgerMaster.getValidatedLedgerAge() > tuning::kMaxValidatedLedgerAge; } template @@ -282,19 +282,19 @@ getLedger(T& ledger, LedgerShortcut shortcut, Context const& context) return {RpcNotSynced, "notSynced"}; } - XRPL_ASSERT(!ledger->open(), "xrpl::RPC::getLedger : validated is not open"); + XRPL_ASSERT(!ledger->open(), "xrpl::rpc::getLedger : validated is not open"); } else { if (shortcut == LedgerShortcut::Current) { ledger = context.ledgerMaster.getCurrentLedger(); - XRPL_ASSERT(ledger->open(), "xrpl::RPC::getLedger : current is open"); + XRPL_ASSERT(ledger->open(), "xrpl::rpc::getLedger : current is open"); } else if (shortcut == LedgerShortcut::Closed) { ledger = context.ledgerMaster.getClosedLedger(); - XRPL_ASSERT(!ledger->open(), "xrpl::RPC::getLedger : closed is not open"); + XRPL_ASSERT(!ledger->open(), "xrpl::rpc::getLedger : closed is not open"); } else { @@ -386,7 +386,7 @@ lookupLedger(std::shared_ptr& ledger, JsonContext const& context } std::expected, json::Value> -getOrAcquireLedger(RPC::JsonContext const& context) +getOrAcquireLedger(rpc::JsonContext const& context) { auto const hasHash = context.params.isMember(jss::ledger_hash); auto const hasIndex = context.params.isMember(jss::ledger_index); @@ -398,7 +398,7 @@ getOrAcquireLedger(RPC::JsonContext const& context) if ((static_cast(hasHash) + static_cast(hasIndex)) != 1) { return std::unexpected( - RPC::makeParamError( + rpc::makeParamError( "Exactly one of 'ledger_hash' or " "'ledger_index' can be specified.")); } @@ -407,16 +407,16 @@ getOrAcquireLedger(RPC::JsonContext const& context) { auto const& jsonHash = context.params.get(jss::ledger_hash, json::ValueType::Null); if (!jsonHash.isString() || !ledgerHash.parseHex(jsonHash.asString())) - return std::unexpected(RPC::expectedFieldError(jss::ledger_hash, "hex string")); + return std::unexpected(rpc::expectedFieldError(jss::ledger_hash, "hex string")); } else { auto const& jsonIndex = context.params.get(jss::ledger_index, json::ValueType::Null); if (!jsonIndex.isInt() && !jsonIndex.isUInt()) - return std::unexpected(RPC::expectedFieldError(jss::ledger_index, "number")); + return std::unexpected(rpc::expectedFieldError(jss::ledger_index, "number")); // We need a validated ledger to get the hash from the sequence - if (ledgerMaster.getValidatedLedgerAge() > RPC::Tuning::kMaxValidatedLedgerAge) + if (ledgerMaster.getValidatedLedgerAge() > rpc::tuning::kMaxValidatedLedgerAge) { if (context.apiVersion == 1) return std::unexpected(rpcError(RpcNoCurrent)); @@ -427,9 +427,9 @@ getOrAcquireLedger(RPC::JsonContext const& context) auto ledger = ledgerMaster.getValidatedLedger(); if (ledgerIndex >= ledger->header().seq) - return std::unexpected(RPC::makeParamError("Ledger index too large")); + return std::unexpected(rpc::makeParamError("Ledger index too large")); if (ledgerIndex <= 0) - return std::unexpected(RPC::makeParamError("Ledger index too small")); + return std::unexpected(rpc::makeParamError("Ledger index too small")); auto const j = context.app.getJournal("RPCHandler"); // Try to get the hash of the desired ledger from the validated @@ -441,7 +441,7 @@ getOrAcquireLedger(RPC::JsonContext const& context) // ledger auto const refIndex = getCandidateLedger(ledgerIndex); auto refHash = hashOfSeq(*ledger, refIndex, j); - XRPL_ASSERT(refHash, "xrpl::RPC::getOrAcquireLedger : nonzero ledger hash"); + XRPL_ASSERT(refHash, "xrpl::rpc::getOrAcquireLedger : nonzero ledger hash"); // NOLINTBEGIN(bugprone-unchecked-optional-access) assert above ledger = ledgerMaster.getLedgerByHash(*refHash); @@ -453,7 +453,7 @@ getOrAcquireLedger(RPC::JsonContext const& context) if (auto il = context.app.getInboundLedgers().acquire( *refHash, refIndex, InboundLedger::Reason::GENERIC)) { - json::Value jvResult = RPC::makeError( + json::Value jvResult = rpc::makeError( RpcLgrNotFound, "acquiring ledger containing requested index"); jvResult[jss::acquiring] = getJson(LedgerFill(*il, &context)); return std::unexpected(jvResult); @@ -462,7 +462,7 @@ getOrAcquireLedger(RPC::JsonContext const& context) if (auto il = context.app.getInboundLedgers().find(*refHash)) // NOLINTEND(bugprone-unchecked-optional-access) { - json::Value jvResult = RPC::makeError( + json::Value jvResult = rpc::makeError( RpcLgrNotFound, "acquiring ledger containing requested index"); jvResult[jss::acquiring] = il->getJson(0); return std::unexpected(jvResult); @@ -474,7 +474,7 @@ getOrAcquireLedger(RPC::JsonContext const& context) neededHash = hashOfSeq(*ledger, ledgerIndex, j); } - XRPL_ASSERT(neededHash, "xrpl::RPC::getOrAcquireLedger : nonzero needed hash"); + XRPL_ASSERT(neededHash, "xrpl::rpc::getOrAcquireLedger : nonzero needed hash"); ledgerHash = neededHash ? *neededHash : beast::kZero; // kludge } @@ -494,7 +494,7 @@ getOrAcquireLedger(RPC::JsonContext const& context) return std::unexpected(il->getJson(0)); return std::unexpected( - RPC::makeError(RpcNotReady, "findCreate failed to return an inbound ledger")); + rpc::makeError(RpcNotReady, "findCreate failed to return an inbound ledger")); } -} // namespace xrpl::RPC +} // namespace xrpl::rpc diff --git a/src/xrpld/rpc/detail/RPCLedgerHelpers.h b/src/xrpld/rpc/detail/RPCLedgerHelpers.h index cbd47d38e6..cad5141917 100644 --- a/src/xrpld/rpc/detail/RPCLedgerHelpers.h +++ b/src/xrpld/rpc/detail/RPCLedgerHelpers.h @@ -21,7 +21,7 @@ namespace xrpl { class ReadView; class Transaction; -namespace RPC { +namespace rpc { struct JsonContext; @@ -172,8 +172,8 @@ ledgerFromSpecifier( * On failure, contains a json::Value describing the error. */ std::expected, json::Value> -getOrAcquireLedger(RPC::JsonContext const& context); +getOrAcquireLedger(rpc::JsonContext const& context); -} // namespace RPC +} // namespace rpc } // namespace xrpl diff --git a/src/xrpld/rpc/detail/RPCSub.cpp b/src/xrpld/rpc/detail/RPCSub.cpp index 92e9849939..8cea8b6bdd 100644 --- a/src/xrpld/rpc/detail/RPCSub.cpp +++ b/src/xrpld/rpc/detail/RPCSub.cpp @@ -75,7 +75,7 @@ public: } path_ = pUrl.path; - JLOG(j_.info()) << "RPCCall::fromNetwork sub: ip=" << ip_ << " port=" << port_ + JLOG(j_.info()) << "rpc_call::fromNetwork sub: ip=" << ip_ << " port=" << port_ << " ssl= " << (ssl_ ? "yes" : "no") << " path='" << path_ << "'"; } @@ -87,14 +87,14 @@ public: std::scoped_lock const sl(lock_); auto jm = broadcast ? j_.debug() : j_.info(); - JLOG(jm) << "RPCCall::fromNetwork push: " << jvObj; + JLOG(jm) << "rpc_call::fromNetwork push: " << jvObj; deque_.emplace_back(seq_++, jvObj); if (!sending_) { // Start a sending thread. - JLOG(j_.info()) << "RPCCall::fromNetwork start"; + JLOG(j_.info()) << "rpc_call::fromNetwork start"; sending_ = jobQueue_.addJob(JtClientSubscribe, "RPCSubSendThr", [this]() { sendThread(); }); @@ -156,9 +156,9 @@ private: // XXX Might not need this in a try. try { - JLOG(j_.info()) << "RPCCall::fromNetwork: " << ip_; + JLOG(j_.info()) << "rpc_call::fromNetwork: " << ip_; - RPCCall::fromNetwork( + rpc_call::fromNetwork( ioContext_, ip_, port_, @@ -173,7 +173,7 @@ private: } catch (std::exception const& e) { - JLOG(j_.info()) << "RPCCall::fromNetwork exception: " << e.what(); + JLOG(j_.info()) << "rpc_call::fromNetwork exception: " << e.what(); } } } while (bSend); diff --git a/src/xrpld/rpc/detail/Role.cpp b/src/xrpld/rpc/detail/Role.cpp index 68c5fcc484..34970b0580 100644 --- a/src/xrpld/rpc/detail/Role.cpp +++ b/src/xrpld/rpc/detail/Role.cpp @@ -40,7 +40,7 @@ passwordUnrequiredOrSentCorrect(Port const& port, json::Value const& params) bool ipAllowed( - beast::IP::Address const& remoteIp, + beast::ip::Address const& remoteIp, std::vector const& nets4, std::vector const& nets6) { @@ -78,7 +78,7 @@ ipAllowed( } bool -isAdmin(Port const& port, json::Value const& params, beast::IP::Address const& remoteIp) +isAdmin(Port const& port, json::Value const& params, beast::ip::Address const& remoteIp) { return ipAllowed(remoteIp, port.adminNetsV4, port.adminNetsV6) && passwordUnrequiredOrSentCorrect(port, params); @@ -89,7 +89,7 @@ requestRole( Role const& required, Port const& port, json::Value const& params, - beast::IP::Endpoint const& remoteIp, + beast::ip::Endpoint const& remoteIp, std::string_view user) { if (isAdmin(port, params, remoteIp.address())) @@ -122,16 +122,16 @@ isUnlimited( Role const& required, Port const& port, json::Value const& params, - beast::IP::Endpoint const& remoteIp, + beast::ip::Endpoint const& remoteIp, std::string const& user) { return isUnlimited(requestRole(required, port, params, remoteIp, user)); } -Resource::Consumer +resource::Consumer requestInboundEndpoint( - Resource::Manager& manager, - beast::IP::Endpoint const& remoteAddress, + resource::Manager& manager, + beast::ip::Endpoint const& remoteAddress, Role const& role, std::string_view user, std::string_view forwardedFor) diff --git a/src/xrpld/rpc/detail/ServerHandler.cpp b/src/xrpld/rpc/detail/ServerHandler.cpp index 768cbf0dc0..0181d5b10f 100644 --- a/src/xrpld/rpc/detail/ServerHandler.cpp +++ b/src/xrpld/rpc/detail/ServerHandler.cpp @@ -93,7 +93,7 @@ statusRequestResponse(http_request_type const& request, boost::beast::http::stat response msg; msg.version(request.version()); msg.result(status); - msg.insert("Server", BuildInfo::getFullVersionString()); + msg.insert("Server", build_info::getFullVersionString()); msg.insert("Content-Type", "text/html"); msg.insert("Connection", "close"); msg.body() = "Invalid protocol."; @@ -129,7 +129,7 @@ ServerHandler::ServerHandler( boost::asio::io_context& ioContext, JobQueue& jobQueue, NetworkOPs& networkOPs, - Resource::Manager& resourceManager, + resource::Manager& resourceManager, CollectorManager& cm) : app_(app) , resourceManager_(resourceManager) @@ -337,7 +337,7 @@ ServerHandler::onWSMessage( { json::Value jv; auto const size = boost::asio::buffer_size(buffers); - if (size > RPC::Tuning::kMaxRequestSize || !json::Reader{}.parse(jv, buffers) || !jv.isObject()) + if (size > rpc::tuning::kMaxRequestSize || !json::Reader{}.parse(jv, buffers) || !jv.isObject()) { json::Value jvResult(json::ValueType::Object); jvResult[jss::type] = jss::error; @@ -426,11 +426,11 @@ ServerHandler::processSession( // Requests without "command" are invalid. json::Value jr(json::ValueType::Object); - Resource::Charge loadType = Resource::kFeeReferenceRpc; + resource::Charge loadType = resource::kFeeReferenceRpc; try { - auto apiVersion = RPC::getAPIVersionNumber(jv, app_.config().betaRpcApi); - if (apiVersion == RPC::kApiInvalidVersion || + auto apiVersion = rpc::getAPIVersionNumber(jv, app_.config().betaRpcApi); + if (apiVersion == rpc::kApiInvalidVersion || (!jv.isMember(jss::command) && !jv.isMember(jss::method)) || (jv.isMember(jss::command) && !jv[jss::command].isString()) || (jv.isMember(jss::method) && !jv[jss::method].isString()) || @@ -439,7 +439,7 @@ ServerHandler::processSession( { jr[jss::type] = jss::response; jr[jss::status] = jss::error; - jr[jss::error] = apiVersion == RPC::kApiInvalidVersion ? jss::invalid_API_version + jr[jss::error] = apiVersion == rpc::kApiInvalidVersion ? jss::invalid_API_version : jss::missingCommand; jr[jss::request] = jv; if (jv.isMember(jss::id)) @@ -451,11 +451,11 @@ ServerHandler::processSession( if (jv.isMember(jss::api_version)) jr[jss::api_version] = jv[jss::api_version]; - is->getConsumer().charge(Resource::kFeeMalformedRpc); + is->getConsumer().charge(resource::kFeeMalformedRpc); return jr; } - auto required = RPC::roleRequired( + auto required = rpc::roleRequired( apiVersion, app_.config().betaRpcApi, jv.isMember(jss::command) ? jv[jss::command].asString() : jv[jss::method].asString()); @@ -463,16 +463,16 @@ ServerHandler::processSession( required, session->port(), jv, - beast::IP::fromAsio(session->remoteEndpoint().address()), + beast::ip::fromAsio(session->remoteEndpoint().address()), is->user()); if (Role::FORBID == role) { - loadType = Resource::kFeeMalformedRpc; + loadType = resource::kFeeMalformedRpc; jr[jss::result] = rpcError(RpcForbidden); } else { - RPC::JsonContext context{ + rpc::JsonContext context{ {.j = app_.getJournal("RPCHandler"), .app = app_, .loadType = loadType, @@ -487,7 +487,7 @@ ServerHandler::processSession( {.user = is->user(), .forwardedFor = is->forwardedFor()}}; auto start = std::chrono::system_clock::now(); - RPC::doCommand(context, jr[jss::result]); + rpc::doCommand(context, jr[jss::result]); auto end = std::chrono::system_clock::now(); logDuration(jv, end - start, journal_); } @@ -495,7 +495,7 @@ ServerHandler::processSession( catch (std::exception const& ex) { // LCOV_EXCL_START - jr[jss::result] = RPC::makeError(RpcInternal); + jr[jss::result] = rpc::makeError(RpcInternal); JLOG(journal_.error()) << "Exception while processing WS: " << ex.what() << "\n" << "Input JSON: " << json::Compact{json::Value{jv}}; // LCOV_EXCL_STOP @@ -601,7 +601,7 @@ void ServerHandler::processRequest( Port const& port, std::string const& request, - beast::IP::Endpoint const& remoteIPAddress, + beast::ip::Endpoint const& remoteIPAddress, Output const& output, std::shared_ptr coro, std::string_view forwardedFor, @@ -612,7 +612,7 @@ ServerHandler::processRequest( json::Value jsonOrig; { json::Reader reader; - if ((request.size() > RPC::Tuning::kMaxRequestSize) || !reader.parse(request, jsonOrig) || + if ((request.size() > rpc::tuning::kMaxRequestSize) || !reader.parse(request, jsonOrig) || !jsonOrig || !jsonOrig.isObject()) { httpReply( @@ -652,21 +652,21 @@ ServerHandler::processRequest( continue; } - unsigned apiVersion = RPC::kApiVersionIfUnspecified; + unsigned apiVersion = rpc::kApiVersionIfUnspecified; if (jsonRPC.isMember(jss::params) && jsonRPC[jss::params].isArray() && jsonRPC[jss::params].size() > 0 && jsonRPC[jss::params][0u].isObject()) { - apiVersion = RPC::getAPIVersionNumber( + apiVersion = rpc::getAPIVersionNumber( jsonRPC[jss::params][json::UInt(0)], app_.config().betaRpcApi); } - if (apiVersion == RPC::kApiVersionIfUnspecified && batch) + if (apiVersion == rpc::kApiVersionIfUnspecified && batch) { // for batch request, api_version may be at a different level - apiVersion = RPC::getAPIVersionNumber(jsonRPC, app_.config().betaRpcApi); + apiVersion = rpc::getAPIVersionNumber(jsonRPC, app_.config().betaRpcApi); } - if (apiVersion == RPC::kApiInvalidVersion) + if (apiVersion == rpc::kApiInvalidVersion) { if (!batch) { @@ -685,7 +685,7 @@ ServerHandler::processRequest( auto required = Role::FORBID; if (jsonRPC.isMember(jss::method) && jsonRPC[jss::method].isString()) { - required = RPC::roleRequired( + required = rpc::roleRequired( apiVersion, app_.config().betaRpcApi, jsonRPC[jss::method].asString()); } @@ -700,7 +700,7 @@ ServerHandler::processRequest( role = requestRole(required, port, json::ValueType::Object, remoteIPAddress, user); } - Resource::Consumer usage; + resource::Consumer usage; if (isUnlimited(role)) { usage = resourceManager_.newUnlimitedEndpoint(remoteIPAddress); @@ -725,7 +725,7 @@ ServerHandler::processRequest( if (role == Role::FORBID) { - usage.charge(Resource::kFeeMalformedRpc); + usage.charge(resource::kFeeMalformedRpc); if (!batch) { httpReply(403, "Forbidden", output, rpcJ); @@ -739,7 +739,7 @@ ServerHandler::processRequest( if (!jsonRPC.isMember(jss::method) || jsonRPC[jss::method].isNull()) { - usage.charge(Resource::kFeeMalformedRpc); + usage.charge(resource::kFeeMalformedRpc); if (!batch) { httpReply(400, "Null method", output, rpcJ); @@ -754,7 +754,7 @@ ServerHandler::processRequest( json::Value const& method = jsonRPC[jss::method]; if (!method.isString()) { - usage.charge(Resource::kFeeMalformedRpc); + usage.charge(resource::kFeeMalformedRpc); if (!batch) { httpReply(400, "method is not string", output, rpcJ); @@ -769,7 +769,7 @@ ServerHandler::processRequest( std::string const strMethod = method.asString(); if (strMethod.empty()) { - usage.charge(Resource::kFeeMalformedRpc); + usage.charge(resource::kFeeMalformedRpc); if (!batch) { httpReply(400, "method is empty", output, rpcJ); @@ -797,7 +797,7 @@ ServerHandler::processRequest( } else if (!params.isArray() || params.size() != 1) { - usage.charge(Resource::kFeeMalformedRpc); + usage.charge(resource::kFeeMalformedRpc); httpReply(400, "params unparsable", output, rpcJ); return; } @@ -806,7 +806,7 @@ ServerHandler::processRequest( params = std::move(params[0u]); if (!params.isObjectOrNull()) { - usage.charge(Resource::kFeeMalformedRpc); + usage.charge(resource::kFeeMalformedRpc); httpReply(400, "params unparsable", output, rpcJ); return; } @@ -822,7 +822,7 @@ ServerHandler::processRequest( { if (!params[jss::ripplerpc].isString()) { - usage.charge(Resource::kFeeMalformedRpc); + usage.charge(resource::kFeeMalformedRpc); if (!batch) { httpReply(400, "ripplerpc is not a string", output, rpcJ); @@ -853,9 +853,9 @@ ServerHandler::processRequest( params[jss::command] = strMethod; JLOG(journal_.trace()) << "doRpcCommand:" << strMethod << ":" << params; - Resource::Charge loadType = Resource::kFeeReferenceRpc; + resource::Charge loadType = resource::kFeeReferenceRpc; - RPC::JsonContext context{ + rpc::JsonContext context{ {.j = journal_, .app = app_, .loadType = loadType, @@ -874,12 +874,12 @@ ServerHandler::processRequest( try { - RPC::doCommand(context, result); + rpc::doCommand(context, result); } catch (std::exception const& ex) { // LCOV_EXCL_START - result = RPC::makeError(RpcInternal); + result = rpc::makeError(RpcInternal); JLOG(journal_.error()) << "Internal error : " << ex.what() << " when processing request: " << json::Compact{json::Value{params}}; @@ -984,7 +984,7 @@ ServerHandler::processRequest( reply[jss::error][jss::error_code].isInt()) { int const errCode = reply[jss::error][jss::error_code].asInt(); - return RPC::errorCodeHttpStatus(static_cast(errCode)); + return rpc::errorCodeHttpStatus(static_cast(errCode)); } } // Return OK. @@ -1043,7 +1043,7 @@ ServerHandler::statusResponse(http_request_type const& request) const msg.body() = "Server cannot accept clients: " + reason + ""; } msg.version(request.version()); - msg.insert("Server", BuildInfo::getFullVersionString()); + msg.insert("Server", build_info::getFullVersionString()); msg.insert("Content-Type", "text/html"); msg.insert("Connection", "close"); msg.prepare_payload(); @@ -1208,7 +1208,7 @@ setupClient(ServerHandler::Setup& setup) if (iter == setup.ports.cend()) return; setup.client.secure = iter->protocol.contains("https"); - if (beast::IP::isUnspecified(iter->ip)) + if (beast::ip::isUnspecified(iter->ip)) { // VFALCO HACK! to make localhost work setup.client.ip = iter->ip.is_v6() ? "::1" : "127.0.0.1"; @@ -1256,7 +1256,7 @@ makeServerHandler( boost::asio::io_context& ioContext, JobQueue& jobQueue, NetworkOPs& networkOPs, - Resource::Manager& resourceManager, + resource::Manager& resourceManager, CollectorManager& cm) { return std::make_unique( diff --git a/src/xrpld/rpc/detail/Status.cpp b/src/xrpld/rpc/detail/Status.cpp index 58d2f8cb80..147f2b31e0 100644 --- a/src/xrpld/rpc/detail/Status.cpp +++ b/src/xrpld/rpc/detail/Status.cpp @@ -9,7 +9,7 @@ #include #include -namespace xrpl::RPC { +namespace xrpl::rpc { std::string Status::codeString() const @@ -25,7 +25,7 @@ Status::codeString() const std::string s1, s2; [[maybe_unused]] auto const success = transResultInfo(toTER(), s1, s2); - XRPL_ASSERT(success, "xrpl::RPC::codeString : valid TER result"); + XRPL_ASSERT(success, "xrpl::rpc::codeString : valid TER result"); return s1 + ": " + s2; } @@ -39,7 +39,7 @@ Status::codeString() const } // LCOV_EXCL_START - UNREACHABLE("xrpl::RPC::codeString : invalid type"); + UNREACHABLE("xrpl::rpc::codeString : invalid type"); return ""; // LCOV_EXCL_STOP } @@ -85,4 +85,4 @@ Status::toString() const return ""; } -} // namespace xrpl::RPC +} // namespace xrpl::rpc diff --git a/src/xrpld/rpc/detail/TransactionSign.cpp b/src/xrpld/rpc/detail/TransactionSign.cpp index e1c5180b5c..9c97577b27 100644 --- a/src/xrpld/rpc/detail/TransactionSign.cpp +++ b/src/xrpld/rpc/detail/TransactionSign.cpp @@ -64,7 +64,7 @@ #include #include -namespace xrpl::RPC { +namespace xrpl::rpc { namespace detail { // Used to pass extra parameters used when returning a @@ -218,7 +218,7 @@ checkPayment( { if (txJson[jss::DeliverMax] != txJson[jss::Amount]) { - return RPC::makeError( + return rpc::makeError( RpcInvalidParams, "Cannot specify differing 'Amount' and 'DeliverMax'"); } } @@ -231,31 +231,31 @@ checkPayment( } if (!txJson.isMember(jss::Amount)) - return RPC::missingFieldError("tx_json.Amount"); + return rpc::missingFieldError("tx_json.Amount"); STAmount amount; if (!amountFromJsonNoThrow(amount, txJson[jss::Amount])) - return RPC::invalidFieldError("tx_json.Amount"); + return rpc::invalidFieldError("tx_json.Amount"); if (!txJson.isMember(jss::Destination)) - return RPC::missingFieldError("tx_json.Destination"); + return rpc::missingFieldError("tx_json.Destination"); auto const dstAccountID = parseBase58(txJson[jss::Destination].asString()); if (!dstAccountID) - return RPC::invalidFieldError("tx_json.Destination"); + return rpc::invalidFieldError("tx_json.Destination"); if (params.isMember(jss::build_path) && (!doPath || (!app.getOpenLedger().current()->rules().enabled(featureMPTokensV2) && amount.holds()))) { - return RPC::makeError(RpcInvalidParams, "Field 'build_path' not allowed in this context."); + return rpc::makeError(RpcInvalidParams, "Field 'build_path' not allowed in this context."); } if (txJson.isMember(jss::Paths) && params.isMember(jss::build_path)) { - return RPC::makeError( + return rpc::makeError( RpcInvalidParams, "Cannot specify both 'tx_json.Paths' and 'build_path'"); } @@ -266,7 +266,7 @@ checkPayment( if (!txJson[sfDomainID.jsonName].isString() || !num.parseHex(txJson[sfDomainID.jsonName].asString())) { - return RPC::makeError(RpcDomainMalformed, "Unable to parse 'DomainID'."); + return rpc::makeError(RpcDomainMalformed, "Unable to parse 'DomainID'."); } domain = num; @@ -279,7 +279,7 @@ checkPayment( if (txJson.isMember(jss::SendMax)) { if (!amountFromJsonNoThrow(sendMax, txJson[jss::SendMax])) - return RPC::invalidFieldError("tx_json.SendMax"); + return rpc::invalidFieldError("tx_json.SendMax"); } else { @@ -291,7 +291,7 @@ checkPayment( } if (sendMax.native() && amount.native()) - return RPC::makeError(RpcInvalidParams, "Cannot build XRP to XRP paths."); + return rpc::makeError(RpcInvalidParams, "Cannot build XRP to XRP paths."); { LegacyPathFind const lpf(isUnlimited(role), app); @@ -357,19 +357,19 @@ checkTxJsonFields( if (!txJson.isObject()) { - ret.first = RPC::objectFieldError(jss::tx_json); + ret.first = rpc::objectFieldError(jss::tx_json); return ret; } if (!txJson.isMember(jss::TransactionType)) { - ret.first = RPC::missingFieldError("tx_json.TransactionType"); + ret.first = rpc::missingFieldError("tx_json.TransactionType"); return ret; } if (!txJson.isMember(jss::Account)) { - ret.first = RPC::makeError(RpcSrcActMissing, RPC::missingFieldMessage("tx_json.Account")); + ret.first = rpc::makeError(RpcSrcActMissing, rpc::missingFieldMessage("tx_json.Account")); return ret; } @@ -377,12 +377,12 @@ checkTxJsonFields( if (!srcAddressID) { - ret.first = RPC::makeError(RpcSrcActMalformed, RPC::invalidFieldMessage("tx_json.Account")); + ret.first = rpc::makeError(RpcSrcActMalformed, rpc::invalidFieldMessage("tx_json.Account")); return ret; } // Check for current ledger. - if (verify && !config.standalone() && (validatedLedgerAge > Tuning::kMaxValidatedLedgerAge)) + if (verify && !config.standalone() && (validatedLedgerAge > tuning::kMaxValidatedLedgerAge)) { if (apiVersion == 1) { @@ -415,12 +415,12 @@ checkNetworkID(json::Value const& txJson, uint32_t appNetworkId) if (!txJson.isMember(jss::NetworkID)) { return std::unexpected( - RPC::makeError(RpcInvalidParams, RPC::missingFieldMessage("tx_json.NetworkID"))); + rpc::makeError(RpcInvalidParams, rpc::missingFieldMessage("tx_json.NetworkID"))); } if (!txJson[jss::NetworkID].isIntegral() || txJson[jss::NetworkID].asUInt() != appNetworkId) { return std::unexpected( - RPC::makeError(RpcInvalidParams, RPC::invalidFieldMessage("tx_json.NetworkID"))); + rpc::makeError(RpcInvalidParams, rpc::invalidFieldMessage("tx_json.NetworkID"))); } } return std::expected(); @@ -490,13 +490,13 @@ transactionPreProcessImpl( { if (signatureTemplate == nullptr) { // Invalid target field - return RPC::makeError(RpcInvalidParams, signatureTarget->get().getName()); + return rpc::makeError(RpcInvalidParams, signatureTarget->get().getName()); } signingArgs.setSignatureTarget(signatureTarget); } if (!params.isMember(jss::tx_json)) - return RPC::missingFieldError(jss::tx_json); + return rpc::missingFieldError(jss::tx_json); json::Value& txJson(params[jss::tx_json]); @@ -510,14 +510,14 @@ transactionPreProcessImpl( app.getFeeTrack(), getAPIVersionNumber(params, app.config().betaRpcApi)); - if (RPC::containsError(txJsonResult)) + if (rpc::containsError(txJsonResult)) return std::move(txJsonResult); // This test covers the case where we're offline so the sequence number // cannot be determined locally. If we're offline then the caller must // provide the sequence number. if (!verify && !txJson.isMember(jss::Sequence)) - return RPC::missingFieldError("tx_json.Sequence"); + return rpc::missingFieldError("tx_json.Sequence"); SLE::const_pointer sle; if (verify) @@ -565,7 +565,7 @@ transactionPreProcessImpl( app.getTxQ(), app); - if (RPC::containsError(err)) + if (rpc::containsError(err)) return err; } @@ -573,7 +573,7 @@ transactionPreProcessImpl( json::Value err = checkPayment( params, txJson, srcAddressID, role, app, verify && signingArgs.editFields()); - if (RPC::containsError(err)) + if (rpc::containsError(err)) return err; } @@ -614,8 +614,8 @@ transactionPreProcessImpl( if (!ptrDelegatedAddressID) { - return RPC::makeError( - RpcSrcActMalformed, RPC::invalidFieldMessage("tx_json.Delegate")); + return rpc::makeError( + RpcSrcActMalformed, rpc::invalidFieldMessage("tx_json.Delegate")); } auto delegatedAddressID = *ptrDelegatedAddressID; @@ -672,17 +672,17 @@ transactionPreProcessImpl( } catch (STObject::FieldErr const& err) { - return RPC::makeError(RpcInvalidParams, err.what()); + return rpc::makeError(RpcInvalidParams, err.what()); } catch (std::exception&) { - return RPC::makeError( + return rpc::makeError( RpcInternal, "Exception occurred constructing serialized transaction"); } std::string reason; if (!passesLocalChecks(*stTx, reason)) - return RPC::makeError(RpcInvalidParams, reason); + return rpc::makeError(RpcInvalidParams, reason); // If multisign then return multiSignature, else set TxnSignature field. if (signingArgs.isMultiSigning()) @@ -716,7 +716,7 @@ transactionConstructImpl( tpTrans = std::make_shared(stTx, reason, app); if (tpTrans->getStatus() != TransStatus::NEW) { - ret.first = RPC::makeError(RpcInternal, "Unable to construct transaction: " + reason); + ret.first = rpc::makeError(RpcInternal, "Unable to construct transaction: " + reason); return ret; } } @@ -741,7 +741,7 @@ transactionConstructImpl( } if (checkValidity(app.getHashRouter(), *sttxNew, rules).first != Validity::Valid) { - ret.first = RPC::makeError(RpcInternal, "Invalid signature."); + ret.first = rpc::makeError(RpcInternal, "Invalid signature."); return ret; } @@ -766,7 +766,7 @@ transactionConstructImpl( if (!tpTrans) { - ret.first = RPC::makeError(RpcInternal, "Unable to sterilize transaction."); + ret.first = rpc::makeError(RpcInternal, "Unable to sterilize transaction."); return ret; } ret.second = std::move(tpTrans); @@ -789,7 +789,7 @@ transactionFormatResultImpl(Transaction::pointer tpTrans, unsigned apiVersion) jvResult[jss::tx_json] = tpTrans->getJson(JsonOptions::Values::None); } - RPC::insertDeliverMax( + rpc::insertDeliverMax( jvResult[jss::tx_json], tpTrans->getSTransaction()->getTxnType(), apiVersion); jvResult[jss::tx_blob] = strHex(tpTrans->getSTransaction()->getSerializer().peekData()); @@ -808,7 +808,7 @@ transactionFormatResultImpl(Transaction::pointer tpTrans, unsigned apiVersion) } catch (std::exception&) { - jvResult = RPC::makeError(RpcInternal, "Exception occurred during JSON handling."); + jvResult = rpc::makeError(RpcInternal, "Exception occurred during JSON handling."); } return jvResult; } @@ -922,7 +922,7 @@ getCurrentNetworkFee( { std::stringstream ss; ss << "Fee of " << fee << " exceeds the requested tx limit of " << *limit; - return RPC::makeError(RpcHighFee, ss.str()); + return rpc::makeError(RpcHighFee, ss.str()); } return fee.jsonClipped(); @@ -943,10 +943,10 @@ checkFee( return json::Value(); if (!doAutoFill) - return RPC::missingFieldError("tx_json.Fee"); + return rpc::missingFieldError("tx_json.Fee"); - int mult = Tuning::kDefaultAutoFillFeeMultiplier; - int div = Tuning::kDefaultAutoFillFeeDivisor; + int mult = tuning::kDefaultAutoFillFeeMultiplier; + int div = tuning::kDefaultAutoFillFeeDivisor; if (request.isMember(jss::fee_mult_max)) { if (request[jss::fee_mult_max].isInt()) @@ -954,15 +954,15 @@ checkFee( mult = request[jss::fee_mult_max].asInt(); if (mult < 0) { - return RPC::makeError( + return rpc::makeError( RpcInvalidParams, - RPC::expectedFieldMessage(jss::fee_mult_max, "a positive integer")); + rpc::expectedFieldMessage(jss::fee_mult_max, "a positive integer")); } } else { - return RPC::makeError( - RpcHighFee, RPC::expectedFieldMessage(jss::fee_mult_max, "a positive integer")); + return rpc::makeError( + RpcHighFee, rpc::expectedFieldMessage(jss::fee_mult_max, "a positive integer")); } } if (request.isMember(jss::fee_div_max)) @@ -972,15 +972,15 @@ checkFee( div = request[jss::fee_div_max].asInt(); if (div <= 0) { - return RPC::makeError( + return rpc::makeError( RpcInvalidParams, - RPC::expectedFieldMessage(jss::fee_div_max, "a positive integer")); + rpc::expectedFieldMessage(jss::fee_div_max, "a positive integer")); } } else { - return RPC::makeError( - RpcHighFee, RPC::expectedFieldMessage(jss::fee_div_max, "a positive integer")); + return rpc::makeError( + RpcHighFee, rpc::expectedFieldMessage(jss::fee_div_max, "a positive integer")); } } @@ -1071,7 +1071,7 @@ transactionSubmit( } catch (std::exception&) { - return RPC::makeError(RpcInternal, "Exception occurred during transaction submission."); + return rpc::makeError(RpcInternal, "Exception occurred during transaction submission."); } return transactionFormatResultImpl(txn.second, apiVersion); @@ -1084,21 +1084,21 @@ static json::Value checkMultiSignFields(json::Value const& jvRequest) { if (!jvRequest.isMember(jss::tx_json)) - return RPC::missingFieldError(jss::tx_json); + return rpc::missingFieldError(jss::tx_json); json::Value const& txJson(jvRequest[jss::tx_json]); if (!txJson.isObject()) - return RPC::invalidFieldMessage(jss::tx_json); + return rpc::invalidFieldMessage(jss::tx_json); // There are a couple of additional fields we need to check before // we serialize. If we serialize first then we generate less useful // error messages. if (!txJson.isMember(jss::Sequence)) - return RPC::missingFieldError("tx_json.Sequence"); + return rpc::missingFieldError("tx_json.Sequence"); if (!txJson.isMember(sfSigningPubKey.getJsonName())) - return RPC::missingFieldError("tx_json.SigningPubKey"); + return rpc::missingFieldError("tx_json.SigningPubKey"); // Multi-signing into a signature_target object field is fine, // because it means the signature is not for the transaction @@ -1106,7 +1106,7 @@ checkMultiSignFields(json::Value const& jvRequest) if (!jvRequest.isMember(jss::signature_target) && !txJson[sfSigningPubKey.getJsonName()].asString().empty()) { - return RPC::makeError( + return rpc::makeError( RpcInvalidParams, "When multi-signing 'tx_json.SigningPubKey' must be empty."); } @@ -1120,7 +1120,7 @@ static json::Value sortAndValidateSigners(STArray& signers, AccountID const& signingForID) { if (signers.empty()) - return RPC::makeParamError("Signers array may not be empty."); + return rpc::makeParamError("Signers array may not be empty."); // Signers must be sorted by Account. std::ranges::sort(signers, [](STObject const& a, STObject const& b) { @@ -1137,7 +1137,7 @@ sortAndValidateSigners(STArray& signers, AccountID const& signingForID) std::ostringstream err; err << "Duplicate Signers:Signer:Account entries (" << toBase58((*dupIter)[sfAccount]) << ") are not allowed."; - return RPC::makeParamError(err.str()); + return rpc::makeParamError(err.str()); } // An account may not sign for itself. @@ -1147,7 +1147,7 @@ sortAndValidateSigners(STArray& signers, AccountID const& signingForID) { std::ostringstream err; err << "A Signer may not be the transaction's Account (" << toBase58(signingForID) << ")."; - return RPC::makeParamError(err.str()); + return rpc::makeParamError(err.str()); } return {}; } @@ -1174,23 +1174,23 @@ transactionSignFor( char const accountField[] = "account"; if (!jvRequest.isMember(accountField)) - return RPC::missingFieldError(accountField); + return rpc::missingFieldError(accountField); // Turn the signer's account into an AccountID for multi-sign. auto const signerAccountID = parseBase58(jvRequest[accountField].asString()); if (!signerAccountID) { - return RPC::makeError(RpcSrcActMalformed, RPC::invalidFieldMessage(accountField)); + return rpc::makeError(RpcSrcActMalformed, rpc::invalidFieldMessage(accountField)); } if (!jvRequest.isMember(jss::tx_json)) - return RPC::missingFieldError(jss::tx_json); + return rpc::missingFieldError(jss::tx_json); { json::Value& txJson(jvRequest[jss::tx_json]); if (!txJson.isObject()) - return RPC::objectFieldError(jss::tx_json); + return rpc::objectFieldError(jss::tx_json); if (auto checkResult = detail::checkNetworkID(txJson, app.getNetworkIDService().getNetworkID()); @@ -1211,7 +1211,7 @@ transactionSignFor( using namespace detail; { json::Value err = checkMultiSignFields(jvRequest); - if (RPC::containsError(err)) + if (rpc::containsError(err)) return err; } @@ -1225,7 +1225,7 @@ transactionSignFor( return preprocResult.first; XRPL_ASSERT( - signForParams.validMultiSign(), "xrpl::RPC::transactionSignFor : valid multi-signature"); + signForParams.validMultiSign(), "xrpl::rpc::transactionSignFor : valid multi-signature"); { SLE::const_pointer const accountState = ledger->read(keylet::account(*signerAccountID)); @@ -1263,7 +1263,7 @@ transactionSignFor( // For delegated transactions, the delegate account is // the one forbidden from appearing in its own Signers array. auto err = sortAndValidateSigners(signers, sttx->getInitiator()); - if (RPC::containsError(err)) + if (rpc::containsError(err)) return err; } @@ -1299,7 +1299,7 @@ transactionSubmitMultiSigned( using namespace detail; { json::Value err = checkMultiSignFields(jvRequest); - if (RPC::containsError(err)) + if (rpc::containsError(err)) return err; } @@ -1314,7 +1314,7 @@ transactionSubmitMultiSigned( app.getFeeTrack(), getAPIVersionNumber(jvRequest, app.config().betaRpcApi)); - if (RPC::containsError(txJsonResult)) + if (rpc::containsError(txJsonResult)) return std::move(txJsonResult); SLE::const_pointer const sle = ledger->read(keylet::account(srcAddressID)); @@ -1332,12 +1332,12 @@ transactionSubmitMultiSigned( json::Value err = checkFee(jvRequest, role, false, app.config(), app.getFeeTrack(), app.getTxQ(), app); - if (RPC::containsError(err)) + if (rpc::containsError(err)) return err; err = checkPayment(jvRequest, txJson, srcAddressID, role, app, false); - if (RPC::containsError(err)) + if (rpc::containsError(err)) return err; } @@ -1359,17 +1359,17 @@ transactionSubmitMultiSigned( } catch (STObject::FieldErr const& err) { - return RPC::makeError(RpcInvalidParams, err.what()); + return rpc::makeError(RpcInvalidParams, err.what()); } catch (std::exception& ex) { std::string const reason(ex.what()); - return RPC::makeError( + return rpc::makeError( RpcInternal, "Exception while serializing transaction: " + reason); } std::string reason; if (!passesLocalChecks(*stTx, reason)) - return RPC::makeError(RpcInvalidParams, reason); + return rpc::makeError(RpcInvalidParams, reason); } // Validate the fields in the serialized transaction. @@ -1383,7 +1383,7 @@ transactionSubmitMultiSigned( std::ostringstream err; err << "Invalid " << sfSigningPubKey.fieldName << " field. Field must be empty when multi-signing."; - return RPC::makeError(RpcInvalidParams, err.str()); + return rpc::makeError(RpcInvalidParams, err.str()); } // There may not be a TxnSignature field. @@ -1397,26 +1397,26 @@ transactionSubmitMultiSigned( { std::ostringstream err; err << "Invalid " << sfFee.fieldName << " field. Fees must be specified in XRP."; - return RPC::makeError(RpcInvalidParams, err.str()); + return rpc::makeError(RpcInvalidParams, err.str()); } if (fee <= STAmount{0}) { std::ostringstream err; err << "Invalid " << sfFee.fieldName << " field. Fees must be greater than zero."; - return RPC::makeError(RpcInvalidParams, err.str()); + return rpc::makeError(RpcInvalidParams, err.str()); } } // Verify that the Signers field is present. if (!stTx->isFieldPresent(sfSigners)) - return RPC::missingFieldError("tx_json.Signers"); + return rpc::missingFieldError("tx_json.Signers"); // If the Signers field is present the SField guarantees it to be an array. // Get a reference to the Signers array so we can verify and sort it. auto& signers = stTx->peekFieldArray(sfSigners); if (signers.empty()) - return RPC::makeParamError("tx_json.Signers array may not be empty."); + return rpc::makeParamError("tx_json.Signers array may not be empty."); // The Signers array may only contain Signer objects. if (std::ranges::find_if_not(signers, [](STObject const& obj) { @@ -1427,14 +1427,14 @@ transactionSubmitMultiSigned( obj.isFieldPresent(sfTxnSignature) && obj.getCount() == 3); }) != signers.end()) { - return RPC::makeParamError("Signers array may only contain Signer entries."); + return rpc::makeParamError("Signers array may only contain Signer entries."); } // The array must be sorted and validated. // For delegated transactions, getInitiator() returns sfDelegate, // that account is the one forbidden from appearing in its own Signers array. auto err = sortAndValidateSigners(signers, stTx->getInitiator()); - if (RPC::containsError(err)) + if (rpc::containsError(err)) return err; // Make sure the SerializedTransaction makes a legitimate Transaction. @@ -1452,10 +1452,10 @@ transactionSubmitMultiSigned( } catch (std::exception&) { - return RPC::makeError(RpcInternal, "Exception occurred during transaction submission."); + return rpc::makeError(RpcInternal, "Exception occurred during transaction submission."); } return transactionFormatResultImpl(txn.second, apiVersion); } -} // namespace xrpl::RPC +} // namespace xrpl::rpc diff --git a/src/xrpld/rpc/detail/TransactionSign.h b/src/xrpld/rpc/detail/TransactionSign.h index dcb417dd16..9b07cbde20 100644 --- a/src/xrpld/rpc/detail/TransactionSign.h +++ b/src/xrpld/rpc/detail/TransactionSign.h @@ -20,7 +20,7 @@ class LoadFeeTrack; class Transaction; class TxQ; -namespace RPC { +namespace rpc { json::Value getCurrentNetworkFee( @@ -30,8 +30,8 @@ getCurrentNetworkFee( TxQ const& txQ, Application const& app, json::Value const& tx, - int mult = Tuning::kDefaultAutoFillFeeMultiplier, - int div = Tuning::kDefaultAutoFillFeeDivisor); + int mult = tuning::kDefaultAutoFillFeeMultiplier, + int div = tuning::kDefaultAutoFillFeeDivisor); /** * Fill in the fee on behalf of the client. @@ -140,5 +140,5 @@ transactionSubmitMultiSigned( Application& app, ProcessTransactionFn const& processTransaction); -} // namespace RPC +} // namespace rpc } // namespace xrpl diff --git a/src/xrpld/rpc/detail/Tuning.h b/src/xrpld/rpc/detail/Tuning.h index b904822698..5c47ab4365 100644 --- a/src/xrpld/rpc/detail/Tuning.h +++ b/src/xrpld/rpc/detail/Tuning.h @@ -6,7 +6,7 @@ * Tuned constants. */ /** @{ */ -namespace xrpl::RPC::Tuning { +namespace xrpl::rpc::tuning { /** * Represents RPC limit parameter values that have a min, default and max. @@ -98,5 +98,5 @@ static constexpr int kMaxSrcCur = 18; */ static constexpr int kMaxAutoSrcCur = 88; -} // namespace xrpl::RPC::Tuning +} // namespace xrpl::rpc::tuning /** @} */ diff --git a/src/xrpld/rpc/handlers/ChannelVerify.cpp b/src/xrpld/rpc/handlers/ChannelVerify.cpp index 64f616e829..ab54745a5d 100644 --- a/src/xrpld/rpc/handlers/ChannelVerify.cpp +++ b/src/xrpld/rpc/handlers/ChannelVerify.cpp @@ -27,13 +27,13 @@ namespace xrpl { // signature: signature to verify // } json::Value -doChannelVerify(RPC::JsonContext& context) +doChannelVerify(rpc::JsonContext& context) { auto const& params(context.params); for (auto const& p : {jss::public_key, jss::channel_id, jss::amount, jss::signature}) { if (!params.isMember(p)) - return RPC::missingFieldError(p); + return rpc::missingFieldError(p); } std::optional pk; diff --git a/src/xrpld/rpc/handlers/Handlers.h b/src/xrpld/rpc/handlers/Handlers.h index 7b347b2ecc..d192e8726c 100644 --- a/src/xrpld/rpc/handlers/Handlers.h +++ b/src/xrpld/rpc/handlers/Handlers.h @@ -7,147 +7,147 @@ namespace xrpl { json::Value -doAccountCurrencies(RPC::JsonContext&); +doAccountCurrencies(rpc::JsonContext&); json::Value -doAccountInfo(RPC::JsonContext&); +doAccountInfo(rpc::JsonContext&); json::Value -doAccountLines(RPC::JsonContext&); +doAccountLines(rpc::JsonContext&); json::Value -doAccountChannels(RPC::JsonContext&); +doAccountChannels(rpc::JsonContext&); json::Value -doAccountNFTs(RPC::JsonContext&); +doAccountNFTs(rpc::JsonContext&); json::Value -doAccountObjects(RPC::JsonContext&); +doAccountObjects(rpc::JsonContext&); json::Value -doAccountOffers(RPC::JsonContext&); +doAccountOffers(rpc::JsonContext&); json::Value -doAccountTx(RPC::JsonContext&); +doAccountTx(rpc::JsonContext&); json::Value -doAMMInfo(RPC::JsonContext&); +doAMMInfo(rpc::JsonContext&); json::Value -doBookOffers(RPC::JsonContext&); +doBookOffers(rpc::JsonContext&); json::Value -doBookChanges(RPC::JsonContext&); +doBookChanges(rpc::JsonContext&); json::Value -doBlackList(RPC::JsonContext&); +doBlackList(rpc::JsonContext&); json::Value -doCanDelete(RPC::JsonContext&); +doCanDelete(rpc::JsonContext&); json::Value -doChannelAuthorize(RPC::JsonContext&); +doChannelAuthorize(rpc::JsonContext&); json::Value -doChannelVerify(RPC::JsonContext&); +doChannelVerify(rpc::JsonContext&); json::Value -doConnect(RPC::JsonContext&); +doConnect(rpc::JsonContext&); json::Value -doConsensusInfo(RPC::JsonContext&); +doConsensusInfo(rpc::JsonContext&); json::Value -doDepositAuthorized(RPC::JsonContext&); +doDepositAuthorized(rpc::JsonContext&); json::Value -doFeature(RPC::JsonContext&); +doFeature(rpc::JsonContext&); json::Value -doFee(RPC::JsonContext&); +doFee(rpc::JsonContext&); json::Value -doFetchInfo(RPC::JsonContext&); +doFetchInfo(rpc::JsonContext&); json::Value -doGatewayBalances(RPC::JsonContext&); +doGatewayBalances(rpc::JsonContext&); json::Value -doGetCounts(RPC::JsonContext&); +doGetCounts(rpc::JsonContext&); json::Value -doGetAggregatePrice(RPC::JsonContext&); +doGetAggregatePrice(rpc::JsonContext&); json::Value -doLedgerAccept(RPC::JsonContext&); +doLedgerAccept(rpc::JsonContext&); json::Value -doLedgerCleaner(RPC::JsonContext&); +doLedgerCleaner(rpc::JsonContext&); json::Value -doLedgerClosed(RPC::JsonContext&); +doLedgerClosed(rpc::JsonContext&); json::Value -doLedgerCurrent(RPC::JsonContext&); +doLedgerCurrent(rpc::JsonContext&); json::Value -doLedgerData(RPC::JsonContext&); +doLedgerData(rpc::JsonContext&); json::Value -doLedgerEntry(RPC::JsonContext&); +doLedgerEntry(rpc::JsonContext&); json::Value -doLedgerHeader(RPC::JsonContext&); +doLedgerHeader(rpc::JsonContext&); json::Value -doLedgerRequest(RPC::JsonContext&); +doLedgerRequest(rpc::JsonContext&); json::Value -doLogLevel(RPC::JsonContext&); +doLogLevel(rpc::JsonContext&); json::Value -doLogRotate(RPC::JsonContext&); +doLogRotate(rpc::JsonContext&); json::Value -doManifest(RPC::JsonContext&); +doManifest(rpc::JsonContext&); json::Value -doNFTBuyOffers(RPC::JsonContext&); +doNFTBuyOffers(rpc::JsonContext&); json::Value -doNFTSellOffers(RPC::JsonContext&); +doNFTSellOffers(rpc::JsonContext&); json::Value -doNoRippleCheck(RPC::JsonContext&); +doNoRippleCheck(rpc::JsonContext&); json::Value -doOwnerInfo(RPC::JsonContext&); +doOwnerInfo(rpc::JsonContext&); json::Value -doPathFind(RPC::JsonContext&); +doPathFind(rpc::JsonContext&); json::Value -doPause(RPC::JsonContext&); +doPause(rpc::JsonContext&); json::Value -doPeers(RPC::JsonContext&); +doPeers(rpc::JsonContext&); json::Value -doPing(RPC::JsonContext&); +doPing(rpc::JsonContext&); json::Value -doPrint(RPC::JsonContext&); +doPrint(rpc::JsonContext&); json::Value -doRandom(RPC::JsonContext&); +doRandom(rpc::JsonContext&); json::Value -doResume(RPC::JsonContext&); +doResume(rpc::JsonContext&); json::Value -doPeerReservationsAdd(RPC::JsonContext&); +doPeerReservationsAdd(rpc::JsonContext&); json::Value -doPeerReservationsDel(RPC::JsonContext&); +doPeerReservationsDel(rpc::JsonContext&); json::Value -doPeerReservationsList(RPC::JsonContext&); +doPeerReservationsList(rpc::JsonContext&); json::Value -doRipplePathFind(RPC::JsonContext&); +doRipplePathFind(rpc::JsonContext&); json::Value -doServerDefinitions(RPC::JsonContext&); +doServerDefinitions(rpc::JsonContext&); json::Value -doServerInfo(RPC::JsonContext&); // for humans +doServerInfo(rpc::JsonContext&); // for humans json::Value -doServerState(RPC::JsonContext&); // for machines +doServerState(rpc::JsonContext&); // for machines json::Value -doSign(RPC::JsonContext&); +doSign(rpc::JsonContext&); json::Value -doSignFor(RPC::JsonContext&); +doSignFor(rpc::JsonContext&); json::Value -doSimulate(RPC::JsonContext&); +doSimulate(rpc::JsonContext&); json::Value -doStop(RPC::JsonContext&); +doStop(rpc::JsonContext&); json::Value -doSubmit(RPC::JsonContext&); +doSubmit(rpc::JsonContext&); json::Value -doSubmitMultiSigned(RPC::JsonContext&); +doSubmitMultiSigned(rpc::JsonContext&); json::Value -doSubscribe(RPC::JsonContext&); +doSubscribe(rpc::JsonContext&); json::Value -doTransactionEntry(RPC::JsonContext&); +doTransactionEntry(rpc::JsonContext&); json::Value -doTxJson(RPC::JsonContext&); +doTxJson(rpc::JsonContext&); json::Value -doTxHistory(RPC::JsonContext&); +doTxHistory(rpc::JsonContext&); json::Value -doTxReduceRelay(RPC::JsonContext&); +doTxReduceRelay(rpc::JsonContext&); json::Value -doUnlList(RPC::JsonContext&); +doUnlList(rpc::JsonContext&); json::Value -doUnsubscribe(RPC::JsonContext&); +doUnsubscribe(rpc::JsonContext&); json::Value -doValidationCreate(RPC::JsonContext&); +doValidationCreate(rpc::JsonContext&); json::Value -doWalletPropose(RPC::JsonContext&); +doWalletPropose(rpc::JsonContext&); json::Value -doValidators(RPC::JsonContext&); +doValidators(rpc::JsonContext&); json::Value -doValidatorListSites(RPC::JsonContext&); +doValidatorListSites(rpc::JsonContext&); json::Value -doValidatorInfo(RPC::JsonContext&); +doValidatorInfo(rpc::JsonContext&); json::Value -doVaultInfo(RPC::JsonContext&); +doVaultInfo(rpc::JsonContext&); } // namespace xrpl diff --git a/src/xrpld/rpc/handlers/VaultInfo.cpp b/src/xrpld/rpc/handlers/VaultInfo.cpp index b6d2fe259f..034cd8383f 100644 --- a/src/xrpld/rpc/handlers/VaultInfo.cpp +++ b/src/xrpld/rpc/handlers/VaultInfo.cpp @@ -27,7 +27,7 @@ parseVault(json::Value const& params, json::Value& jvResult) { if (!uNodeIndex.parseHex(params[jss::vault_id].asString())) { - RPC::injectError(RpcInvalidParams, jvResult); + rpc::injectError(RpcInvalidParams, jvResult); return std::nullopt; } // else uNodeIndex holds the value we need @@ -37,14 +37,14 @@ parseVault(json::Value const& params, json::Value& jvResult) auto const id = parseBase58(params[jss::owner].asString()); if (!id) { - RPC::injectError(RpcActMalformed, jvResult); + rpc::injectError(RpcActMalformed, jvResult); return std::nullopt; } if (!(params[jss::seq].isInt() || params[jss::seq].isUInt()) || params[jss::seq].asDouble() <= 0.0 || params[jss::seq].asDouble() > double(json::Value::kMaxUInt)) { - RPC::injectError(RpcInvalidParams, jvResult); + rpc::injectError(RpcInvalidParams, jvResult); return std::nullopt; } @@ -53,7 +53,7 @@ parseVault(json::Value const& params, json::Value& jvResult) else { // Invalid combination of fields vault_id/owner/seq - RPC::injectError(RpcInvalidParams, jvResult); + rpc::injectError(RpcInvalidParams, jvResult); return std::nullopt; } @@ -61,10 +61,10 @@ parseVault(json::Value const& params, json::Value& jvResult) } json::Value -doVaultInfo(RPC::JsonContext& context) +doVaultInfo(rpc::JsonContext& context) { std::shared_ptr lpLedger; - auto jvResult = RPC::lookupLedger(lpLedger, context); + auto jvResult = rpc::lookupLedger(lpLedger, context); if (!lpLedger) return jvResult; diff --git a/src/xrpld/rpc/handlers/account/AccountChannels.cpp b/src/xrpld/rpc/handlers/account/AccountChannels.cpp index 8a5c7dc6e3..d50bf1cf07 100644 --- a/src/xrpld/rpc/handlers/account/AccountChannels.cpp +++ b/src/xrpld/rpc/handlers/account/AccountChannels.cpp @@ -69,17 +69,17 @@ addChannel(json::Value& jsonLines, SLE const& line) // marker: opaque // optional, resume previous query // } json::Value -doAccountChannels(RPC::JsonContext& context) +doAccountChannels(rpc::JsonContext& context) { auto const& params(context.params); if (!params.isMember(jss::account)) - return RPC::missingFieldError(jss::account); + return rpc::missingFieldError(jss::account); if (!params[jss::account].isString()) - return RPC::invalidFieldError(jss::account); + return rpc::invalidFieldError(jss::account); std::shared_ptr ledger; - auto result = RPC::lookupLedger(ledger, context); + auto result = rpc::lookupLedger(ledger, context); if (!ledger) return result; @@ -97,7 +97,7 @@ doAccountChannels(RPC::JsonContext& context) if (params.isMember(jss::destination_account)) { if (!params[jss::destination_account].isString()) - return RPC::invalidFieldError(jss::destination_account); + return rpc::invalidFieldError(jss::destination_account); strDst = params[jss::destination_account].asString(); } @@ -108,7 +108,7 @@ doAccountChannels(RPC::JsonContext& context) return rpcError(RpcActMalformed); unsigned int limit = 0; - if (auto err = readLimitField(limit, RPC::Tuning::kAccountChannels, context)) + if (auto err = readLimitField(limit, rpc::tuning::kAccountChannels, context)) return *err; json::Value jsonChannels{json::ValueType::Array}; @@ -126,7 +126,7 @@ doAccountChannels(RPC::JsonContext& context) if (params.isMember(jss::marker)) { if (!params[jss::marker].isString()) - return RPC::expectedFieldError(jss::marker, "string"); + return rpc::expectedFieldError(jss::marker, "string"); // Marker is composed of a comma separated index and start hint. The // former will be read as hex, and the latter using boost lexical cast. @@ -157,7 +157,7 @@ doAccountChannels(RPC::JsonContext& context) if (!sle) return rpcError(RpcInvalidParams); - if (!RPC::isRelatedToAccount(*ledger, sle, accountID)) + if (!rpc::isRelatedToAccount(*ledger, sle, accountID)) return rpcError(RpcInvalidParams); } @@ -182,7 +182,7 @@ doAccountChannels(RPC::JsonContext& context) if (++count == limit) { marker = sleCur->key(); - nextHint = RPC::getStartHint(sleCur, visitData.accountID); + nextHint = rpc::getStartHint(sleCur, visitData.accountID); } if (count <= limit && sleCur->getType() == ltPAYCHAN && @@ -213,7 +213,7 @@ doAccountChannels(RPC::JsonContext& context) for (auto const& item : visitData.items) addChannel(jsonChannels, *item); - context.loadType = Resource::kFeeMediumBurdenRpc; + context.loadType = resource::kFeeMediumBurdenRpc; result[jss::channels] = std::move(jsonChannels); return result; } diff --git a/src/xrpld/rpc/handlers/account/AccountCurrencies.cpp b/src/xrpld/rpc/handlers/account/AccountCurrencies.cpp index 058c10e224..d9cd41cbbc 100644 --- a/src/xrpld/rpc/handlers/account/AccountCurrencies.cpp +++ b/src/xrpld/rpc/handlers/account/AccountCurrencies.cpp @@ -19,30 +19,30 @@ namespace xrpl { json::Value -doAccountCurrencies(RPC::JsonContext& context) +doAccountCurrencies(rpc::JsonContext& context) { auto& params = context.params; if (!(params.isMember(jss::account) || params.isMember(jss::ident))) - return RPC::missingFieldError(jss::account); + return rpc::missingFieldError(jss::account); std::string strIdent; if (params.isMember(jss::account)) { if (!params[jss::account].isString()) - return RPC::invalidFieldError(jss::account); + return rpc::invalidFieldError(jss::account); strIdent = params[jss::account].asString(); } else if (params.isMember(jss::ident)) { if (!params[jss::ident].isString()) - return RPC::invalidFieldError(jss::ident); + return rpc::invalidFieldError(jss::ident); strIdent = params[jss::ident].asString(); } // Get the current ledger std::shared_ptr ledger; - auto result = RPC::lookupLedger(ledger, context); + auto result = rpc::lookupLedger(ledger, context); if (!ledger) return result; @@ -50,7 +50,7 @@ doAccountCurrencies(RPC::JsonContext& context) auto id = parseBase58(strIdent); if (!id) { - RPC::injectError(RpcActMalformed, result); + rpc::injectError(RpcActMalformed, result); return result; } auto const accountID{id.value()}; diff --git a/src/xrpld/rpc/handlers/account/AccountInfo.cpp b/src/xrpld/rpc/handlers/account/AccountInfo.cpp index 3a96593452..c618ad3b3a 100644 --- a/src/xrpld/rpc/handlers/account/AccountInfo.cpp +++ b/src/xrpld/rpc/handlers/account/AccountInfo.cpp @@ -84,7 +84,7 @@ injectSLE(json::Value& jv, SLE const& sle) // TODO(tom): what is that "default"? json::Value -doAccountInfo(RPC::JsonContext& context) +doAccountInfo(rpc::JsonContext& context) { auto& params = context.params; @@ -92,22 +92,22 @@ doAccountInfo(RPC::JsonContext& context) if (params.isMember(jss::account)) { if (!params[jss::account].isString()) - return RPC::invalidFieldError(jss::account); + return rpc::invalidFieldError(jss::account); strIdent = params[jss::account].asString(); } else if (params.isMember(jss::ident)) { if (!params[jss::ident].isString()) - return RPC::invalidFieldError(jss::ident); + return rpc::invalidFieldError(jss::ident); strIdent = params[jss::ident].asString(); } else { - return RPC::missingFieldError(jss::account); + return rpc::missingFieldError(jss::account); } std::shared_ptr ledger; - auto result = RPC::lookupLedger(ledger, context); + auto result = rpc::lookupLedger(ledger, context); if (!ledger) return result; @@ -116,7 +116,7 @@ doAccountInfo(RPC::JsonContext& context) auto id = parseBase58(strIdent); if (!id) { - RPC::injectError(RpcActMalformed, result); + rpc::injectError(RpcActMalformed, result); return result; } auto const accountID{id.value()}; @@ -154,7 +154,7 @@ doAccountInfo(RPC::JsonContext& context) { // It doesn't make sense to request the queue // with any closed or validated ledger. - RPC::injectError(RpcInvalidParams, result); + rpc::injectError(RpcInvalidParams, result); return result; } @@ -206,7 +206,7 @@ doAccountInfo(RPC::JsonContext& context) if (context.apiVersion > 1u && params.isMember(jss::signer_lists) && !params[jss::signer_lists].isBool()) { - RPC::injectError(RpcInvalidParams, result); + rpc::injectError(RpcInvalidParams, result); return result; } @@ -331,7 +331,7 @@ doAccountInfo(RPC::JsonContext& context) else { result[jss::account] = toBase58(accountID); - RPC::injectError(RpcActNotFound, result); + rpc::injectError(RpcActNotFound, result); } return result; diff --git a/src/xrpld/rpc/handlers/account/AccountLines.cpp b/src/xrpld/rpc/handlers/account/AccountLines.cpp index e69f70ca5a..f134c8af92 100644 --- a/src/xrpld/rpc/handlers/account/AccountLines.cpp +++ b/src/xrpld/rpc/handlers/account/AccountLines.cpp @@ -82,24 +82,24 @@ addLine(json::Value& jsonLines, RPCTrustLine const& line) // this account's side) // } json::Value -doAccountLines(RPC::JsonContext& context) +doAccountLines(rpc::JsonContext& context) { auto const& params(context.params); if (!params.isMember(jss::account)) - return RPC::missingFieldError(jss::account); + return rpc::missingFieldError(jss::account); if (!params[jss::account].isString()) - return RPC::invalidFieldError(jss::account); + return rpc::invalidFieldError(jss::account); std::shared_ptr ledger; - auto result = RPC::lookupLedger(ledger, context); + auto result = rpc::lookupLedger(ledger, context); if (!ledger) return result; auto id = parseBase58(params[jss::account].asString()); if (!id) { - RPC::injectError(RpcActMalformed, result); + rpc::injectError(RpcActMalformed, result); return result; } auto const accountID{id.value()}; @@ -116,12 +116,12 @@ doAccountLines(RPC::JsonContext& context) }(); if (!strPeer.empty() && !raPeerAccount) { - RPC::injectError(RpcActMalformed, result); + rpc::injectError(RpcActMalformed, result); return result; } unsigned int limit = 0; - if (auto err = readLimitField(limit, RPC::Tuning::kAccountLines, context)) + if (auto err = readLimitField(limit, rpc::tuning::kAccountLines, context)) return *err; // this flag allows the requester to ask incoming trustlines in default @@ -150,7 +150,7 @@ doAccountLines(RPC::JsonContext& context) if (params.isMember(jss::marker)) { if (!params[jss::marker].isString()) - return RPC::expectedFieldError(jss::marker, "string"); + return rpc::expectedFieldError(jss::marker, "string"); // Marker is composed of a comma separated index and start hint. The // former will be read as hex, and the latter using boost lexical cast. @@ -181,7 +181,7 @@ doAccountLines(RPC::JsonContext& context) if (!sle) return rpcError(RpcInvalidParams); - if (!RPC::isRelatedToAccount(*ledger, sle, accountID)) + if (!rpc::isRelatedToAccount(*ledger, sle, accountID)) return rpcError(RpcInvalidParams); } @@ -207,7 +207,7 @@ doAccountLines(RPC::JsonContext& context) if (++count == limit) { marker = sleCur->key(); - nextHint = RPC::getStartHint(sleCur, visitData.accountID); + nextHint = rpc::getStartHint(sleCur, visitData.accountID); } if (sleCur->getType() != ltRIPPLE_STATE) @@ -259,7 +259,7 @@ doAccountLines(RPC::JsonContext& context) for (auto const& item : visitData.items) addLine(jsonLines, item); - context.loadType = Resource::kFeeMediumBurdenRpc; + context.loadType = resource::kFeeMediumBurdenRpc; return result; } diff --git a/src/xrpld/rpc/handlers/account/AccountNFTs.cpp b/src/xrpld/rpc/handlers/account/AccountNFTs.cpp index ea9bec0f45..580b93caa3 100644 --- a/src/xrpld/rpc/handlers/account/AccountNFTs.cpp +++ b/src/xrpld/rpc/handlers/account/AccountNFTs.cpp @@ -35,14 +35,14 @@ namespace xrpl { * } */ json::Value -doAccountNFTs(RPC::JsonContext& context) +doAccountNFTs(rpc::JsonContext& context) { auto const& params = context.params; if (!params.isMember(jss::account)) - return RPC::missingFieldError(jss::account); + return rpc::missingFieldError(jss::account); if (!params[jss::account].isString()) - return RPC::invalidFieldError(jss::account); + return rpc::invalidFieldError(jss::account); auto id = parseBase58(params[jss::account].asString()); if (!id) @@ -51,7 +51,7 @@ doAccountNFTs(RPC::JsonContext& context) } std::shared_ptr ledger; - auto result = RPC::lookupLedger(ledger, context); + auto result = rpc::lookupLedger(ledger, context); if (ledger == nullptr) return result; auto const accountID{id.value()}; @@ -60,7 +60,7 @@ doAccountNFTs(RPC::JsonContext& context) return rpcError(RpcActNotFound); unsigned int limit = 0; - if (auto err = readLimitField(limit, RPC::Tuning::kAccountNfTokens, context)) + if (auto err = readLimitField(limit, rpc::tuning::kAccountNfTokens, context)) return *err; uint256 marker; @@ -70,10 +70,10 @@ doAccountNFTs(RPC::JsonContext& context) { auto const& m = params[jss::marker]; if (!m.isString()) - return RPC::expectedFieldError(jss::marker, "string"); + return rpc::expectedFieldError(jss::marker, "string"); if (!marker.parseHex(m.asString())) - return RPC::invalidFieldError(jss::marker); + return rpc::invalidFieldError(jss::marker); } auto const first = keylet::nftokenPage(keylet::nftokenPageMin(accountID), marker); @@ -125,7 +125,7 @@ doAccountNFTs(RPC::JsonContext& context) } if (markerSet && !markerFound) - return RPC::invalidFieldError(jss::marker); + return rpc::invalidFieldError(jss::marker); pastMarker = true; @@ -160,10 +160,10 @@ doAccountNFTs(RPC::JsonContext& context) } if (markerSet && !markerFound) - return RPC::invalidFieldError(jss::marker); + return rpc::invalidFieldError(jss::marker); result[jss::account] = toBase58(accountID); - context.loadType = Resource::kFeeMediumBurdenRpc; + context.loadType = resource::kFeeMediumBurdenRpc; return result; } diff --git a/src/xrpld/rpc/handlers/account/AccountObjects.cpp b/src/xrpld/rpc/handlers/account/AccountObjects.cpp index ee2595bf94..e855ed65e6 100644 --- a/src/xrpld/rpc/handlers/account/AccountObjects.cpp +++ b/src/xrpld/rpc/handlers/account/AccountObjects.cpp @@ -265,24 +265,24 @@ getAccountObjects( } json::Value -doAccountObjects(RPC::JsonContext& context) +doAccountObjects(rpc::JsonContext& context) { auto const& params = context.params; if (!params.isMember(jss::account)) - return RPC::missingFieldError(jss::account); + return rpc::missingFieldError(jss::account); if (!params[jss::account].isString()) - return RPC::invalidFieldError(jss::account); + return rpc::invalidFieldError(jss::account); std::shared_ptr ledger; - auto result = RPC::lookupLedger(ledger, context); + auto result = rpc::lookupLedger(ledger, context); if (ledger == nullptr) return result; auto const id = parseBase58(params[jss::account].asString()); if (!id) { - RPC::injectError(RpcActMalformed, result); + rpc::injectError(RpcActMalformed, result); return result; } auto const accountID{id.value()}; @@ -331,10 +331,10 @@ doAccountObjects(RPC::JsonContext& context) } else { - auto [rpcStatus, type] = RPC::chooseLedgerEntryType(params); + auto [rpcStatus, type] = rpc::chooseLedgerEntryType(params); - if (!RPC::isAccountObjectsValidType(type)) - return RPC::invalidFieldError(jss::type); + if (!rpc::isAccountObjectsValidType(type)) + return rpc::invalidFieldError(jss::type); if (rpcStatus) { @@ -349,7 +349,7 @@ doAccountObjects(RPC::JsonContext& context) } unsigned int limit = 0; - if (auto err = readLimitField(limit, RPC::Tuning::kAccountObjects, context)) + if (auto err = readLimitField(limit, rpc::tuning::kAccountObjects, context)) return *err; uint256 dirIndex; @@ -358,18 +358,18 @@ doAccountObjects(RPC::JsonContext& context) { auto const& marker = params[jss::marker]; if (!marker.isString()) - return RPC::expectedFieldError(jss::marker, "string"); + return rpc::expectedFieldError(jss::marker, "string"); auto const& markerStr = marker.asString(); auto const& idx = markerStr.find(','); if (idx == std::string::npos) - return RPC::invalidFieldError(jss::marker); + return rpc::invalidFieldError(jss::marker); if (!dirIndex.parseHex(markerStr.substr(0, idx))) - return RPC::invalidFieldError(jss::marker); + return rpc::invalidFieldError(jss::marker); if (!entryIndex.parseHex(markerStr.substr(idx + 1))) - return RPC::invalidFieldError(jss::marker); + return rpc::invalidFieldError(jss::marker); } std::optional sponsoredFilter; @@ -377,17 +377,17 @@ doAccountObjects(RPC::JsonContext& context) { auto const& sponsoredJv = params[jss::sponsored]; if (!sponsoredJv.isBool()) - return RPC::expectedFieldError(jss::sponsored, "boolean"); + return rpc::expectedFieldError(jss::sponsored, "boolean"); sponsoredFilter = sponsoredJv.asBool(); } if (!getAccountObjects( *ledger, accountID, typeFilter, dirIndex, entryIndex, limit, sponsoredFilter, result)) - return RPC::invalidFieldError(jss::marker); + return rpc::invalidFieldError(jss::marker); result[jss::account] = toBase58(accountID); - context.loadType = Resource::kFeeMediumBurdenRpc; + context.loadType = resource::kFeeMediumBurdenRpc; return result; } diff --git a/src/xrpld/rpc/handlers/account/AccountOffers.cpp b/src/xrpld/rpc/handlers/account/AccountOffers.cpp index 4829ff56b1..1467b14b48 100644 --- a/src/xrpld/rpc/handlers/account/AccountOffers.cpp +++ b/src/xrpld/rpc/handlers/account/AccountOffers.cpp @@ -54,24 +54,24 @@ appendOfferJson(SLE::const_ref offer, json::Value& offers) // marker: opaque // optional, resume previous query // } json::Value -doAccountOffers(RPC::JsonContext& context) +doAccountOffers(rpc::JsonContext& context) { auto const& params(context.params); if (!params.isMember(jss::account)) - return RPC::missingFieldError(jss::account); + return rpc::missingFieldError(jss::account); if (!params[jss::account].isString()) - return RPC::invalidFieldError(jss::account); + return rpc::invalidFieldError(jss::account); std::shared_ptr ledger; - auto result = RPC::lookupLedger(ledger, context); + auto result = rpc::lookupLedger(ledger, context); if (!ledger) return result; auto id = parseBase58(params[jss::account].asString()); if (!id) { - RPC::injectError(RpcActMalformed, result); + rpc::injectError(RpcActMalformed, result); return result; } auto const accountID{id.value()}; @@ -83,7 +83,7 @@ doAccountOffers(RPC::JsonContext& context) return rpcError(RpcActNotFound); unsigned int limit = 0; - if (auto err = readLimitField(limit, RPC::Tuning::kAccountOffers, context)) + if (auto err = readLimitField(limit, rpc::tuning::kAccountOffers, context)) return *err; json::Value& jsonOffers(result[jss::offers] = json::ValueType::Array); @@ -94,20 +94,20 @@ doAccountOffers(RPC::JsonContext& context) if (params.isMember(jss::marker)) { if (!params[jss::marker].isString()) - return RPC::expectedFieldError(jss::marker, "string"); + return rpc::expectedFieldError(jss::marker, "string"); // Marker is composed of a comma separated index and start hint. The // former will be read as hex, and the latter using boost lexical cast. std::stringstream marker(params[jss::marker].asString()); std::string value; if (!std::getline(marker, value, ',')) - return RPC::invalidFieldError(jss::marker); + return rpc::invalidFieldError(jss::marker); if (!startAfter.parseHex(value)) - return RPC::invalidFieldError(jss::marker); + return rpc::invalidFieldError(jss::marker); if (!std::getline(marker, value, ',')) - return RPC::invalidFieldError(jss::marker); + return rpc::invalidFieldError(jss::marker); try { @@ -115,7 +115,7 @@ doAccountOffers(RPC::JsonContext& context) } catch (boost::bad_lexical_cast&) { - return RPC::invalidFieldError(jss::marker); + return rpc::invalidFieldError(jss::marker); } // We then must check if the object pointed to by the marker is actually @@ -125,7 +125,7 @@ doAccountOffers(RPC::JsonContext& context) if (!sle) return rpcError(RpcInvalidParams); - if (!RPC::isRelatedToAccount(*ledger, sle, accountID)) + if (!rpc::isRelatedToAccount(*ledger, sle, accountID)) return rpcError(RpcInvalidParams); } @@ -150,7 +150,7 @@ doAccountOffers(RPC::JsonContext& context) if (++count == limit) { marker = sle->key(); - nextHint = RPC::getStartHint(sle, accountID); + nextHint = rpc::getStartHint(sle, accountID); } if (count <= limit && sle->getType() == ltOFFER) @@ -176,7 +176,7 @@ doAccountOffers(RPC::JsonContext& context) for (auto const& offer : offers) appendOfferJson(offer, jsonOffers); - context.loadType = Resource::kFeeMediumBurdenRpc; + context.loadType = resource::kFeeMediumBurdenRpc; return result; } diff --git a/src/xrpld/rpc/handlers/account/AccountTx.cpp b/src/xrpld/rpc/handlers/account/AccountTx.cpp index c43f560861..7b0c34e048 100644 --- a/src/xrpld/rpc/handlers/account/AccountTx.cpp +++ b/src/xrpld/rpc/handlers/account/AccountTx.cpp @@ -43,11 +43,11 @@ static std::expected parseDelegateFilter(json::Value const& delegateNode) { if (!delegateNode.isObject()) - return std::unexpected(RPC::invalidFieldError(jss::delegate)); + return std::unexpected(rpc::invalidFieldError(jss::delegate)); if (!delegateNode.isMember(jss::delegate_filter) || !delegateNode[jss::delegate_filter].isString()) - return std::unexpected(RPC::invalidFieldError(jss::delegate_filter)); + return std::unexpected(rpc::invalidFieldError(jss::delegate_filter)); auto const& delegateFilterStr = delegateNode[jss::delegate_filter].asString(); @@ -58,7 +58,7 @@ parseDelegateFilter(json::Value const& delegateNode) if (delegateFilterStr == "authorizer") return DelegateType::Authorizer; - return std::unexpected(RPC::invalidFieldError(jss::delegate_filter)); + return std::unexpected(rpc::invalidFieldError(jss::delegate_filter)); }(); if (!typeResult) @@ -70,7 +70,7 @@ parseDelegateFilter(json::Value const& delegateNode) if (delegateNode.isMember(jss::counter_party)) { if (!delegateNode[jss::counter_party].isString()) - return std::unexpected(RPC::invalidFieldError(jss::counter_party)); + return std::unexpected(rpc::invalidFieldError(jss::counter_party)); counterparty = parseBase58(delegateNode[jss::counter_party].asString()); @@ -90,7 +90,7 @@ using LedgerSpecifier = RelationalDatabase::LedgerSpecifier; // parses args into a ledger specifier, or returns a Json object on error std::variant, json::Value> -parseLedgerArgs(RPC::Context& context, json::Value const& params) +parseLedgerArgs(rpc::Context& context, json::Value const& params) { json::Value response; // if ledger_index_min or max is specified, then ledger_hash or ledger_index @@ -100,7 +100,7 @@ parseLedgerArgs(RPC::Context& context, json::Value const& params) if ((params.isMember(jss::ledger_index_min) || params.isMember(jss::ledger_index_max)) && (params.isMember(jss::ledger_hash) || params.isMember(jss::ledger_index))) { - RPC::Status const status{RpcInvalidParams, "invalidParams"}; + rpc::Status const status{RpcInvalidParams, "invalidParams"}; status.inject(response); return response; } @@ -123,7 +123,7 @@ parseLedgerArgs(RPC::Context& context, json::Value const& params) auto& hashValue = params[jss::ledger_hash]; if (!hashValue.isString()) { - RPC::Status const status{RpcInvalidParams, "ledgerHashNotString"}; + rpc::Status const status{RpcInvalidParams, "ledgerHashNotString"}; status.inject(response); return response; } @@ -131,7 +131,7 @@ parseLedgerArgs(RPC::Context& context, json::Value const& params) LedgerHash hash; if (!hash.parseHex(hashValue.asString())) { - RPC::Status const status{RpcInvalidParams, "ledgerHashMalformed"}; + rpc::Status const status{RpcInvalidParams, "ledgerHashMalformed"}; status.inject(response); return response; } @@ -162,7 +162,7 @@ parseLedgerArgs(RPC::Context& context, json::Value const& params) } else { - RPC::Status const status{RpcInvalidParams, "ledger_index string malformed"}; + rpc::Status const status{RpcInvalidParams, "ledger_index string malformed"}; status.inject(response); return response; } @@ -172,8 +172,8 @@ parseLedgerArgs(RPC::Context& context, json::Value const& params) return std::optional{}; } -std::variant -getLedgerRange(RPC::Context& context, std::optional const& ledgerSpecifier) +std::variant +getLedgerRange(rpc::Context& context, std::optional const& ledgerSpecifier) { std::uint32_t uValidatedMin = 0; std::uint32_t uValidatedMax = 0; @@ -193,7 +193,7 @@ getLedgerRange(RPC::Context& context, std::optional const& ledg if (ledgerSpecifier) { auto status = std::visit( - [&](auto const& ls) -> RPC::Status { + [&](auto const& ls) -> rpc::Status { using T = std::decay_t; if constexpr (std::is_same_v) { @@ -241,7 +241,7 @@ getLedgerRange(RPC::Context& context, std::optional const& ledg } uLedgerMin = uLedgerMax = ledgerView->header().seq; } - return RPC::Status::kOK; + return rpc::Status::kOK; }, *ledgerSpecifier); @@ -251,15 +251,15 @@ getLedgerRange(RPC::Context& context, std::optional const& ledg return LedgerRange{.min = uLedgerMin, .max = uLedgerMax}; } -std::pair -doAccountTxHelp(RPC::Context& context, AccountTxArgs const& args) +std::pair +doAccountTxHelp(rpc::Context& context, AccountTxArgs const& args) { - context.loadType = Resource::kFeeMediumBurdenRpc; + context.loadType = resource::kFeeMediumBurdenRpc; AccountTxResult result; auto lgrRange = getLedgerRange(context, args.ledger); - if (auto stat = std::get_if(&lgrRange)) + if (auto stat = std::get_if(&lgrRange)) { // An error occurred getting the requested ledger range return {result, *stat}; @@ -318,12 +318,12 @@ doAccountTxHelp(RPC::Context& context, AccountTxArgs const& args) json::Value populateJsonResponse( - std::pair const& res, + std::pair const& res, AccountTxArgs const& args, - RPC::JsonContext const& context) + rpc::JsonContext const& context) { json::Value response; - RPC::Status const& error = res.second; + rpc::Status const& error = res.second; if (error.toErrorCode() != RpcSuccess) { error.inject(response); @@ -374,13 +374,13 @@ populateJsonResponse( } auto const& sttx = txn->getSTransaction(); - RPC::insertDeliverMax(jvObj[jsonTx], sttx->getTxnType(), context.apiVersion); + rpc::insertDeliverMax(jvObj[jsonTx], sttx->getTxnType(), context.apiVersion); if (txnMeta) { jvObj[jss::meta] = txnMeta->getJson(JsonOptions::Values::IncludeDate); insertDeliveredAmount(jvObj[jss::meta], context, txn, *txnMeta); - RPC::insertNFTSyntheticInJson(jvObj, sttx, *txnMeta); - RPC::insertMPTokenIssuanceID(jvObj[jss::meta], sttx, *txnMeta); + rpc::insertNFTSyntheticInJson(jvObj, sttx, *txnMeta); + rpc::insertMPTokenIssuanceID(jvObj[jss::meta], sttx, *txnMeta); } else { @@ -445,7 +445,7 @@ populateJsonResponse( // delegate-filtered query is only valid for a follow-up request that repeats // the same `delegate` object json::Value -doAccountTx(RPC::JsonContext& context) +doAccountTx(rpc::JsonContext& context) { if (!context.app.config().useTxTables()) return rpcError(RpcNotEnabled); @@ -460,24 +460,24 @@ doAccountTx(RPC::JsonContext& context) // onwards only if (context.apiVersion > 1u && params.isMember(jss::binary) && !params[jss::binary].isBool()) { - return RPC::invalidFieldError(jss::binary); + return rpc::invalidFieldError(jss::binary); } if (context.apiVersion > 1u && params.isMember(jss::forward) && !params[jss::forward].isBool()) { - return RPC::invalidFieldError(jss::forward); + return rpc::invalidFieldError(jss::forward); } - if (auto const err = RPC::readLimitField(args.limit, RPC::Tuning::kAccountTx, context)) + if (auto const err = rpc::readLimitField(args.limit, rpc::tuning::kAccountTx, context)) return *err; args.binary = params.isMember(jss::binary) && params[jss::binary].asBool(); args.forward = params.isMember(jss::forward) && params[jss::forward].asBool(); if (!params.isMember(jss::account)) - return RPC::missingFieldError(jss::account); + return rpc::missingFieldError(jss::account); if (!params[jss::account].isString()) - return RPC::invalidFieldError(jss::account); + return rpc::invalidFieldError(jss::account); auto const account = parseBase58(params[jss::account].asString()); if (!account) @@ -500,7 +500,7 @@ doAccountTx(RPC::JsonContext& context) !token[jss::ledger].isConvertibleTo(json::ValueType::UInt) || !token[jss::seq].isConvertibleTo(json::ValueType::UInt)) { - RPC::Status const status{ + rpc::Status const status{ RpcInvalidParams, "invalid marker. Provide ledger index via ledger field, and " "transaction sequence number via seq field"}; @@ -534,7 +534,7 @@ doAccountTx(RPC::JsonContext& context) params[jss::marker][jss::delegate].asBool(); if (markerFromDelegate != args.delegate.has_value()) { - RPC::Status const status{ + rpc::Status const status{ RpcInvalidParams, "Do not mix delegate and non-delegate pagination markers in account_tx; " "repeat the same `delegate` object when using a delegate marker."}; diff --git a/src/xrpld/rpc/handlers/account/GatewayBalances.cpp b/src/xrpld/rpc/handlers/account/GatewayBalances.cpp index a6730c8e2b..ff19d1d1e5 100644 --- a/src/xrpld/rpc/handlers/account/GatewayBalances.cpp +++ b/src/xrpld/rpc/handlers/account/GatewayBalances.cpp @@ -49,19 +49,19 @@ namespace xrpl { // gateway_balances [] [ [ ledger; - auto result = RPC::lookupLedger(ledger, context); + auto result = rpc::lookupLedger(ledger, context); if (!ledger) return result; if (!(params.isMember(jss::account) || params.isMember(jss::ident))) - return RPC::missingFieldError(jss::account); + return rpc::missingFieldError(jss::account); std::string const strIdent( params.isMember(jss::account) ? params[jss::account].asString() @@ -72,13 +72,13 @@ doGatewayBalances(RPC::JsonContext& context) if (!id) return rpcError(RpcActMalformed); auto const accountID{id.value()}; - context.loadType = Resource::kFeeHeavyBurdenRpc; + context.loadType = resource::kFeeHeavyBurdenRpc; result[jss::account] = toBase58(accountID); if (context.apiVersion > 1u && !ledger->exists(keylet::account(accountID))) { - RPC::injectError(RpcActNotFound, result); + rpc::injectError(RpcActNotFound, result); return result; } @@ -126,11 +126,11 @@ doGatewayBalances(RPC::JsonContext& context) // not have currency issued by the account from the request. if (context.apiVersion < 2u) { - RPC::injectError(RpcInvalidHotwallet, result); + rpc::injectError(RpcInvalidHotwallet, result); } else { - RPC::injectError(RpcInvalidParams, result); + rpc::injectError(RpcInvalidParams, result); } return result; } diff --git a/src/xrpld/rpc/handlers/account/NoRippleCheck.cpp b/src/xrpld/rpc/handlers/account/NoRippleCheck.cpp index d8bb65aba9..4be6e6f1af 100644 --- a/src/xrpld/rpc/handlers/account/NoRippleCheck.cpp +++ b/src/xrpld/rpc/handlers/account/NoRippleCheck.cpp @@ -26,7 +26,7 @@ namespace xrpl { static void fillTransaction( - RPC::JsonContext& context, + rpc::JsonContext& context, json::Value& txArray, AccountID const& accountID, std::uint32_t& sequence, @@ -49,17 +49,17 @@ fillTransaction( // transactions: true // optional, recommend transactions // } json::Value -doNoRippleCheck(RPC::JsonContext& context) +doNoRippleCheck(rpc::JsonContext& context) { auto const& params(context.params); if (!params.isMember(jss::account)) - return RPC::missingFieldError("account"); + return rpc::missingFieldError("account"); if (!params.isMember("role")) - return RPC::missingFieldError("role"); + return rpc::missingFieldError("role"); if (!params[jss::account].isString()) - return RPC::invalidFieldError(jss::account); + return rpc::invalidFieldError(jss::account); bool roleGateway = false; { @@ -70,12 +70,12 @@ doNoRippleCheck(RPC::JsonContext& context) } else if (role != "user") { - return RPC::invalidFieldError("role"); + return rpc::invalidFieldError("role"); } } unsigned int limit = 0; - if (auto err = readLimitField(limit, RPC::Tuning::kNoRippleCheck, context)) + if (auto err = readLimitField(limit, rpc::tuning::kNoRippleCheck, context)) return *err; bool transactions = false; @@ -89,11 +89,11 @@ doNoRippleCheck(RPC::JsonContext& context) if (context.apiVersion > 1u && params.isMember(jss::transactions) && !params[jss::transactions].isBool()) { - return RPC::invalidFieldError(jss::transactions); + return rpc::invalidFieldError(jss::transactions); } std::shared_ptr ledger; - auto result = RPC::lookupLedger(ledger, context); + auto result = rpc::lookupLedger(ledger, context); if (!ledger) return result; @@ -104,7 +104,7 @@ doNoRippleCheck(RPC::JsonContext& context) auto id = parseBase58(params[jss::account].asString()); if (!id) { - RPC::injectError(RpcActMalformed, result); + rpc::injectError(RpcActMalformed, result); return result; } auto const accountID{id.value()}; diff --git a/src/xrpld/rpc/handlers/account/OwnerInfo.cpp b/src/xrpld/rpc/handlers/account/OwnerInfo.cpp index 7cfed3cf53..2f5f76c619 100644 --- a/src/xrpld/rpc/handlers/account/OwnerInfo.cpp +++ b/src/xrpld/rpc/handlers/account/OwnerInfo.cpp @@ -17,11 +17,11 @@ namespace xrpl { // 'ident' : , // } json::Value -doOwnerInfo(RPC::JsonContext& context) +doOwnerInfo(rpc::JsonContext& context) { if (!context.params.isMember(jss::account) && !context.params.isMember(jss::ident)) { - return RPC::missingFieldError(jss::account); + return rpc::missingFieldError(jss::account); } std::string const strIdent = context.params.isMember(jss::account) diff --git a/src/xrpld/rpc/handlers/admin/BlackList.cpp b/src/xrpld/rpc/handlers/admin/BlackList.cpp index 5065a41ec4..7a72651373 100644 --- a/src/xrpld/rpc/handlers/admin/BlackList.cpp +++ b/src/xrpld/rpc/handlers/admin/BlackList.cpp @@ -8,7 +8,7 @@ namespace xrpl { json::Value -doBlackList(RPC::JsonContext& context) +doBlackList(rpc::JsonContext& context) { auto& rm = context.app.getResourceManager(); if (context.params.isMember(jss::threshold)) diff --git a/src/xrpld/rpc/handlers/admin/UnlList.cpp b/src/xrpld/rpc/handlers/admin/UnlList.cpp index c3835c7ae0..61b5e4c640 100644 --- a/src/xrpld/rpc/handlers/admin/UnlList.cpp +++ b/src/xrpld/rpc/handlers/admin/UnlList.cpp @@ -12,7 +12,7 @@ namespace xrpl { json::Value -doUnlList(RPC::JsonContext& context) +doUnlList(rpc::JsonContext& context) { json::Value obj(json::ValueType::Object); diff --git a/src/xrpld/rpc/handlers/admin/data/CanDelete.cpp b/src/xrpld/rpc/handlers/admin/data/CanDelete.cpp index 9ec1157e66..91db16bb4f 100644 --- a/src/xrpld/rpc/handlers/admin/data/CanDelete.cpp +++ b/src/xrpld/rpc/handlers/admin/data/CanDelete.cpp @@ -19,10 +19,10 @@ namespace xrpl { // can_delete [||now|always|never] json::Value -doCanDelete(RPC::JsonContext& context) +doCanDelete(rpc::JsonContext& context) { if (!context.app.getSHAMapStore().advisoryDelete()) - return RPC::makeError(RpcNotEnabled); + return rpc::makeError(RpcNotEnabled); json::Value ret(json::ValueType::Object); @@ -56,20 +56,20 @@ doCanDelete(RPC::JsonContext& context) { canDeleteSeq = context.app.getSHAMapStore().getLastRotated(); if (canDeleteSeq == 0u) - return RPC::makeError(RpcNotReady); + return rpc::makeError(RpcNotReady); } else if (uint256 lh; lh.parseHex(canDeleteStr)) { auto ledger = context.ledgerMaster.getLedgerByHash(lh); if (!ledger) - return RPC::makeError(RpcLgrNotFound, "ledgerNotFound"); + return rpc::makeError(RpcLgrNotFound, "ledgerNotFound"); canDeleteSeq = ledger->header().seq; } else { - return RPC::makeError(RpcInvalidParams); + return rpc::makeError(RpcInvalidParams); } } diff --git a/src/xrpld/rpc/handlers/admin/data/LedgerCleaner.cpp b/src/xrpld/rpc/handlers/admin/data/LedgerCleaner.cpp index a62ca44cca..78a2fec410 100644 --- a/src/xrpld/rpc/handlers/admin/data/LedgerCleaner.cpp +++ b/src/xrpld/rpc/handlers/admin/data/LedgerCleaner.cpp @@ -9,10 +9,10 @@ namespace xrpl { json::Value -doLedgerCleaner(RPC::JsonContext& context) +doLedgerCleaner(rpc::JsonContext& context) { context.app.getLedgerCleaner().clean(context.params); - return RPC::makeObjectValue("Cleaner configured"); + return rpc::makeObjectValue("Cleaner configured"); } } // namespace xrpl diff --git a/src/xrpld/rpc/handlers/admin/data/LedgerRequest.cpp b/src/xrpld/rpc/handlers/admin/data/LedgerRequest.cpp index de1e4439cf..80a49aea3f 100644 --- a/src/xrpld/rpc/handlers/admin/data/LedgerRequest.cpp +++ b/src/xrpld/rpc/handlers/admin/data/LedgerRequest.cpp @@ -13,10 +13,10 @@ namespace xrpl { // ledger_index : // } json::Value -doLedgerRequest(RPC::JsonContext& context) +doLedgerRequest(rpc::JsonContext& context) { - context.loadType = Resource::kFeeHeavyBurdenRpc; - auto res = RPC::getOrAcquireLedger(context); + context.loadType = resource::kFeeHeavyBurdenRpc; + auto res = rpc::getOrAcquireLedger(context); if (!res.has_value()) return res.error(); diff --git a/src/xrpld/rpc/handlers/admin/keygen/ValidationCreate.cpp b/src/xrpld/rpc/handlers/admin/keygen/ValidationCreate.cpp index 0849bad944..5ff3e6727e 100644 --- a/src/xrpld/rpc/handlers/admin/keygen/ValidationCreate.cpp +++ b/src/xrpld/rpc/handlers/admin/keygen/ValidationCreate.cpp @@ -30,7 +30,7 @@ validationSeed(json::Value const& params) // This command requires Role::ADMIN access because it makes // no sense to ask an untrusted server for this. json::Value -doValidationCreate(RPC::JsonContext& context) +doValidationCreate(rpc::JsonContext& context) { json::Value obj(json::ValueType::Object); diff --git a/src/xrpld/rpc/handlers/admin/keygen/WalletPropose.cpp b/src/xrpld/rpc/handlers/admin/keygen/WalletPropose.cpp index 4b5f1821e3..62def76f9a 100644 --- a/src/xrpld/rpc/handlers/admin/keygen/WalletPropose.cpp +++ b/src/xrpld/rpc/handlers/admin/keygen/WalletPropose.cpp @@ -52,7 +52,7 @@ estimateEntropy(std::string const& input) // passphrase: // } json::Value -doWalletPropose(RPC::JsonContext& context) +doWalletPropose(rpc::JsonContext& context) { return walletPropose(context.params); } @@ -68,7 +68,7 @@ walletPropose(json::Value const& params) { if (!params[jss::key_type].isString()) { - return RPC::expectedFieldError(jss::key_type, "string"); + return rpc::expectedFieldError(jss::key_type, "string"); } keyType = keyTypeFromString(params[jss::key_type].asString()); @@ -83,11 +83,11 @@ walletPropose(json::Value const& params) { if (params.isMember(jss::passphrase)) { - seed = RPC::parseXrplLibSeed(params[jss::passphrase]); + seed = rpc::parseXrplLibSeed(params[jss::passphrase]); } else if (params.isMember(jss::seed)) { - seed = RPC::parseXrplLibSeed(params[jss::seed]); + seed = rpc::parseXrplLibSeed(params[jss::seed]); } if (seed) @@ -110,7 +110,7 @@ walletPropose(json::Value const& params) { json::Value err; - seed = RPC::getSeedFromRPC(params, err); + seed = rpc::getSeedFromRPC(params, err); if (!seed) return err; diff --git a/src/xrpld/rpc/handlers/admin/log/LogLevel.cpp b/src/xrpld/rpc/handlers/admin/log/LogLevel.cpp index 1ff5aa1a27..4aae350810 100644 --- a/src/xrpld/rpc/handlers/admin/log/LogLevel.cpp +++ b/src/xrpld/rpc/handlers/admin/log/LogLevel.cpp @@ -16,7 +16,7 @@ namespace xrpl { json::Value -doLogLevel(RPC::JsonContext& context) +doLogLevel(rpc::JsonContext& context) { // log_level if (not context.params.isMember(jss::severity)) diff --git a/src/xrpld/rpc/handlers/admin/log/LogRotate.cpp b/src/xrpld/rpc/handlers/admin/log/LogRotate.cpp index 5f5c3e64df..ca935540dc 100644 --- a/src/xrpld/rpc/handlers/admin/log/LogRotate.cpp +++ b/src/xrpld/rpc/handlers/admin/log/LogRotate.cpp @@ -9,10 +9,10 @@ namespace xrpl { json::Value -doLogRotate(RPC::JsonContext& context) +doLogRotate(rpc::JsonContext& context) { context.app.getPerfLog().rotate(); - return RPC::makeObjectValue(context.app.getLogs().rotate()); + return rpc::makeObjectValue(context.app.getLogs().rotate()); } } // namespace xrpl diff --git a/src/xrpld/rpc/handlers/admin/peer/Connect.cpp b/src/xrpld/rpc/handlers/admin/peer/Connect.cpp index 568dcdaa26..b318af06f9 100644 --- a/src/xrpld/rpc/handlers/admin/peer/Connect.cpp +++ b/src/xrpld/rpc/handlers/admin/peer/Connect.cpp @@ -21,15 +21,15 @@ namespace xrpl { // } // XXX Might allow domain for manual connections. json::Value -doConnect(RPC::JsonContext& context) +doConnect(rpc::JsonContext& context) { if (context.app.config().standalone()) { - return RPC::makeError(RpcNotSynced); + return rpc::makeError(RpcNotSynced); } if (!context.params.isMember(jss::ip)) - return RPC::missingFieldError(jss::ip); + return rpc::missingFieldError(jss::ip); if (context.params.isMember(jss::port) && !context.params[jss::port].isConvertibleTo(json::ValueType::Int)) @@ -49,12 +49,12 @@ doConnect(RPC::JsonContext& context) } auto const ipStr = context.params[jss::ip].asString(); - auto ip = beast::IP::Endpoint::fromString(ipStr); + auto ip = beast::ip::Endpoint::fromString(ipStr); if (!isUnspecified(ip)) context.app.getOverlay().connect(ip.atPort(iPort)); - return RPC::makeObjectValue( + return rpc::makeObjectValue( "attempting connection to IP:" + ipStr + " port: " + std::to_string(iPort)); } diff --git a/src/xrpld/rpc/handlers/admin/peer/PeerReservationsAdd.cpp b/src/xrpld/rpc/handlers/admin/peer/PeerReservationsAdd.cpp index 23b0d094b4..579ad85f41 100644 --- a/src/xrpld/rpc/handlers/admin/peer/PeerReservationsAdd.cpp +++ b/src/xrpld/rpc/handlers/admin/peer/PeerReservationsAdd.cpp @@ -15,12 +15,12 @@ namespace xrpl { json::Value -doPeerReservationsAdd(RPC::JsonContext& context) +doPeerReservationsAdd(rpc::JsonContext& context) { auto const& params = context.params; if (!params.isMember(jss::public_key)) - return RPC::missingFieldError(jss::public_key); + return rpc::missingFieldError(jss::public_key); // Returning JSON from every function ruins any attempt to encapsulate // the pattern of "get field F as type T, and diagnose an error if it is @@ -36,7 +36,7 @@ doPeerReservationsAdd(RPC::JsonContext& context) // essentially an optional (the "maybe monad" in Haskell) with a non-unit // type for the failure case to capture more information. if (!params[jss::public_key].isString()) - return RPC::expectedFieldError(jss::public_key, "a string"); + return rpc::expectedFieldError(jss::public_key, "a string"); // Same for the pattern of "if field F is present, make sure it has type T // and get it". @@ -44,7 +44,7 @@ doPeerReservationsAdd(RPC::JsonContext& context) if (params.isMember(jss::description)) { if (!params[jss::description].isString()) - return RPC::expectedFieldError(jss::description, "a string"); + return rpc::expectedFieldError(jss::description, "a string"); desc = params[jss::description].asString(); } diff --git a/src/xrpld/rpc/handlers/admin/peer/PeerReservationsDel.cpp b/src/xrpld/rpc/handlers/admin/peer/PeerReservationsDel.cpp index c2a8319876..e5912a5eca 100644 --- a/src/xrpld/rpc/handlers/admin/peer/PeerReservationsDel.cpp +++ b/src/xrpld/rpc/handlers/admin/peer/PeerReservationsDel.cpp @@ -14,15 +14,15 @@ namespace xrpl { json::Value -doPeerReservationsDel(RPC::JsonContext& context) +doPeerReservationsDel(rpc::JsonContext& context) { auto const& params = context.params; // We repeat much of the parameter parsing from `doPeerReservationsAdd`. if (!params.isMember(jss::public_key)) - return RPC::missingFieldError(jss::public_key); + return rpc::missingFieldError(jss::public_key); if (!params[jss::public_key].isString()) - return RPC::expectedFieldError(jss::public_key, "a string"); + return rpc::expectedFieldError(jss::public_key, "a string"); std::optional optPk = parseBase58(TokenType::NodePublic, params[jss::public_key].asString()); diff --git a/src/xrpld/rpc/handlers/admin/peer/PeerReservationsList.cpp b/src/xrpld/rpc/handlers/admin/peer/PeerReservationsList.cpp index e0204159fd..30e19a5c0b 100644 --- a/src/xrpld/rpc/handlers/admin/peer/PeerReservationsList.cpp +++ b/src/xrpld/rpc/handlers/admin/peer/PeerReservationsList.cpp @@ -8,7 +8,7 @@ namespace xrpl { json::Value -doPeerReservationsList(RPC::JsonContext& context) +doPeerReservationsList(rpc::JsonContext& context) { auto const& reservations = context.app.getPeerReservations().list(); // Enumerate the reservations in context.app.getPeerReservations() diff --git a/src/xrpld/rpc/handlers/admin/peer/Peers.cpp b/src/xrpld/rpc/handlers/admin/peer/Peers.cpp index ab14325f0e..99f069e27e 100644 --- a/src/xrpld/rpc/handlers/admin/peer/Peers.cpp +++ b/src/xrpld/rpc/handlers/admin/peer/Peers.cpp @@ -17,7 +17,7 @@ namespace xrpl { json::Value -doPeers(RPC::JsonContext& context) +doPeers(rpc::JsonContext& context) { json::Value jvResult(json::ValueType::Object); diff --git a/src/xrpld/rpc/handlers/admin/server_control/LedgerAccept.cpp b/src/xrpld/rpc/handlers/admin/server_control/LedgerAccept.cpp index ce06a6e480..00a259bb52 100644 --- a/src/xrpld/rpc/handlers/admin/server_control/LedgerAccept.cpp +++ b/src/xrpld/rpc/handlers/admin/server_control/LedgerAccept.cpp @@ -12,7 +12,7 @@ namespace xrpl { json::Value -doLedgerAccept(RPC::JsonContext& context) +doLedgerAccept(rpc::JsonContext& context) { json::Value jvResult; diff --git a/src/xrpld/rpc/handlers/admin/server_control/Stop.cpp b/src/xrpld/rpc/handlers/admin/server_control/Stop.cpp index 3e86bd4632..949eccf1e1 100644 --- a/src/xrpld/rpc/handlers/admin/server_control/Stop.cpp +++ b/src/xrpld/rpc/handlers/admin/server_control/Stop.cpp @@ -6,15 +6,15 @@ namespace xrpl { -namespace RPC { +namespace rpc { struct JsonContext; -} // namespace RPC +} // namespace rpc json::Value -doStop(RPC::JsonContext& context) +doStop(rpc::JsonContext& context) { context.app.signalStop("RPC"); - return RPC::makeObjectValue(systemName() + " server stopping"); + return rpc::makeObjectValue(systemName() + " server stopping"); } } // namespace xrpl diff --git a/src/xrpld/rpc/handlers/admin/signing/ChannelAuthorize.cpp b/src/xrpld/rpc/handlers/admin/signing/ChannelAuthorize.cpp index be3ce13d45..d97ce9dac4 100644 --- a/src/xrpld/rpc/handlers/admin/signing/ChannelAuthorize.cpp +++ b/src/xrpld/rpc/handlers/admin/signing/ChannelAuthorize.cpp @@ -30,34 +30,34 @@ namespace xrpl { // drops: 64-bit uint (as string) // } json::Value -doChannelAuthorize(RPC::JsonContext& context) +doChannelAuthorize(rpc::JsonContext& context) { if (context.role != Role::ADMIN && !context.app.config().canSign()) { - return RPC::makeError(RpcNotSupported, "Signing is not supported by this server."); + return rpc::makeError(RpcNotSupported, "Signing is not supported by this server."); } auto const& params(context.params); for (auto const& p : {jss::channel_id, jss::amount}) { if (!params.isMember(p)) - return RPC::missingFieldError(p); + return rpc::missingFieldError(p); } // Compatibility if a key type isn't specified. If it is, the // keypairForSignature code will validate parameters and return // the appropriate error. if (!params.isMember(jss::key_type) && !params.isMember(jss::secret)) - return RPC::missingFieldError(jss::secret); + return rpc::missingFieldError(jss::secret); json::Value result; std::optional> const keyPair = - RPC::keypairForSignature(params, result, context.apiVersion); + rpc::keypairForSignature(params, result, context.apiVersion); XRPL_ASSERT( - keyPair || RPC::containsError(result), + keyPair || rpc::containsError(result), "xrpl::doChannelAuthorize : valid keyPair or an error"); - if (!keyPair || RPC::containsError(result)) + if (!keyPair || rpc::containsError(result)) return result; PublicKey const& pk = keyPair->first; @@ -86,7 +86,7 @@ doChannelAuthorize(RPC::JsonContext& context) catch (std::exception const& ex) { // LCOV_EXCL_START - result = RPC::makeError( + result = rpc::makeError( RpcInternal, "Exception occurred during signing: " + std::string(ex.what())); // LCOV_EXCL_STOP } diff --git a/src/xrpld/rpc/handlers/admin/signing/Sign.cpp b/src/xrpld/rpc/handlers/admin/signing/Sign.cpp index 781e160f54..6aac058a56 100644 --- a/src/xrpld/rpc/handlers/admin/signing/Sign.cpp +++ b/src/xrpld/rpc/handlers/admin/signing/Sign.cpp @@ -15,18 +15,18 @@ namespace xrpl { // secret: // } json::Value -doSign(RPC::JsonContext& context) +doSign(rpc::JsonContext& context) { if (context.role != Role::ADMIN && !context.app.config().canSign()) { - return RPC::makeError(RpcNotSupported, "Signing is not supported by this server."); + return rpc::makeError(RpcNotSupported, "Signing is not supported by this server."); } - context.loadType = Resource::kFeeHeavyBurdenRpc; + context.loadType = resource::kFeeHeavyBurdenRpc; NetworkOPs::FailHard const failType = NetworkOPs::doFailHard( context.params.isMember(jss::fail_hard) && context.params[jss::fail_hard].asBool()); - auto ret = RPC::transactionSign( + auto ret = rpc::transactionSign( context.params, context.apiVersion, failType, diff --git a/src/xrpld/rpc/handlers/admin/signing/SignFor.cpp b/src/xrpld/rpc/handlers/admin/signing/SignFor.cpp index 35274d51f4..2b9c830647 100644 --- a/src/xrpld/rpc/handlers/admin/signing/SignFor.cpp +++ b/src/xrpld/rpc/handlers/admin/signing/SignFor.cpp @@ -16,18 +16,18 @@ namespace xrpl { // secret: // } json::Value -doSignFor(RPC::JsonContext& context) +doSignFor(rpc::JsonContext& context) { if (context.role != Role::ADMIN && !context.app.config().canSign()) { - return RPC::makeError(RpcNotSupported, "Signing is not supported by this server."); + return rpc::makeError(RpcNotSupported, "Signing is not supported by this server."); } - context.loadType = Resource::kFeeHeavyBurdenRpc; + context.loadType = resource::kFeeHeavyBurdenRpc; auto const failHard = context.params[jss::fail_hard].asBool(); auto const failType = NetworkOPs::doFailHard(failHard); - auto ret = RPC::transactionSignFor( + auto ret = rpc::transactionSignFor( context.params, context.apiVersion, failType, diff --git a/src/xrpld/rpc/handlers/admin/status/ConsensusInfo.cpp b/src/xrpld/rpc/handlers/admin/status/ConsensusInfo.cpp index 341980ba6d..8017e7058b 100644 --- a/src/xrpld/rpc/handlers/admin/status/ConsensusInfo.cpp +++ b/src/xrpld/rpc/handlers/admin/status/ConsensusInfo.cpp @@ -7,7 +7,7 @@ namespace xrpl { json::Value -doConsensusInfo(RPC::JsonContext& context) +doConsensusInfo(rpc::JsonContext& context) { json::Value ret(json::ValueType::Object); diff --git a/src/xrpld/rpc/handlers/admin/status/FetchInfo.cpp b/src/xrpld/rpc/handlers/admin/status/FetchInfo.cpp index 16e4789025..ca9bff31f5 100644 --- a/src/xrpld/rpc/handlers/admin/status/FetchInfo.cpp +++ b/src/xrpld/rpc/handlers/admin/status/FetchInfo.cpp @@ -7,7 +7,7 @@ namespace xrpl { json::Value -doFetchInfo(RPC::JsonContext& context) +doFetchInfo(rpc::JsonContext& context) { json::Value ret(json::ValueType::Object); diff --git a/src/xrpld/rpc/handlers/admin/status/GetCounts.cpp b/src/xrpld/rpc/handlers/admin/status/GetCounts.cpp index 789c6dcf17..421f23d237 100644 --- a/src/xrpld/rpc/handlers/admin/status/GetCounts.cpp +++ b/src/xrpld/rpc/handlers/admin/status/GetCounts.cpp @@ -111,7 +111,7 @@ getCountsJson(Application& app, int minObjectCount) // min_count: // optional, defaults to 10 // } json::Value -doGetCounts(RPC::JsonContext& context) +doGetCounts(rpc::JsonContext& context) { int minCount = 10; diff --git a/src/xrpld/rpc/handlers/admin/status/Print.cpp b/src/xrpld/rpc/handlers/admin/status/Print.cpp index 99fd01c9f4..1e1f7f0662 100644 --- a/src/xrpld/rpc/handlers/admin/status/Print.cpp +++ b/src/xrpld/rpc/handlers/admin/status/Print.cpp @@ -8,7 +8,7 @@ namespace xrpl { json::Value -doPrint(RPC::JsonContext& context) +doPrint(rpc::JsonContext& context) { JsonPropertyStream stream; if (context.params.isObject() && context.params[jss::params].isArray() && diff --git a/src/xrpld/rpc/handlers/admin/status/ValidatorInfo.cpp b/src/xrpld/rpc/handlers/admin/status/ValidatorInfo.cpp index efe1529ccb..705e03c1a0 100644 --- a/src/xrpld/rpc/handlers/admin/status/ValidatorInfo.cpp +++ b/src/xrpld/rpc/handlers/admin/status/ValidatorInfo.cpp @@ -12,12 +12,12 @@ namespace xrpl { json::Value -doValidatorInfo(RPC::JsonContext& context) +doValidatorInfo(rpc::JsonContext& context) { // return error if not configured as validator auto const validationPK = context.app.getValidationPublicKey(); if (!validationPK) - return RPC::notValidatorError(); + return rpc::notValidatorError(); json::Value ret; diff --git a/src/xrpld/rpc/handlers/admin/status/ValidatorListSites.cpp b/src/xrpld/rpc/handlers/admin/status/ValidatorListSites.cpp index 7497105c50..9bc8b6bcda 100644 --- a/src/xrpld/rpc/handlers/admin/status/ValidatorListSites.cpp +++ b/src/xrpld/rpc/handlers/admin/status/ValidatorListSites.cpp @@ -7,7 +7,7 @@ namespace xrpl { json::Value -doValidatorListSites(RPC::JsonContext& context) +doValidatorListSites(rpc::JsonContext& context) { return context.app.getValidatorSites().getJson(); } diff --git a/src/xrpld/rpc/handlers/admin/status/Validators.cpp b/src/xrpld/rpc/handlers/admin/status/Validators.cpp index 48e4466861..d605a38f1b 100644 --- a/src/xrpld/rpc/handlers/admin/status/Validators.cpp +++ b/src/xrpld/rpc/handlers/admin/status/Validators.cpp @@ -7,7 +7,7 @@ namespace xrpl { json::Value -doValidators(RPC::JsonContext& context) +doValidators(rpc::JsonContext& context) { return context.app.getValidators().getJson(); } diff --git a/src/xrpld/rpc/handlers/ledger/Ledger.cpp b/src/xrpld/rpc/handlers/ledger/Ledger.cpp index 23a97a5026..51f5bdf348 100644 --- a/src/xrpld/rpc/handlers/ledger/Ledger.cpp +++ b/src/xrpld/rpc/handlers/ledger/Ledger.cpp @@ -34,7 +34,7 @@ #include namespace xrpl { -namespace RPC { +namespace rpc { LedgerHandler::LedgerHandler(JsonContext& context) : context_(context) { @@ -107,7 +107,7 @@ LedgerHandler::check() { return RpcTooBusy; } - context_.loadType = binary ? Resource::kFeeMediumBurdenRpc : Resource::kFeeHeavyBurdenRpc; + context_.loadType = binary ? resource::kFeeMediumBurdenRpc : resource::kFeeHeavyBurdenRpc; } if (*queue) @@ -162,10 +162,10 @@ LedgerHandler::writeResult(json::Value& value) value[jss::warnings] = std::move(warnings); } -} // namespace RPC +} // namespace rpc std::pair -doLedgerGrpc(RPC::GRPCContext& context) +doLedgerGrpc(rpc::GRPCContext& context) { auto begin = std::chrono::system_clock::now(); org::xrpl::rpc::v1::GetLedgerRequest const& request = context.params; @@ -173,7 +173,7 @@ doLedgerGrpc(RPC::GRPCContext& context) grpc::Status const status = grpc::Status::OK; std::shared_ptr ledger; - if (auto status = RPC::ledgerFromRequest(ledger, context)) + if (auto status = rpc::ledgerFromRequest(ledger, context)) { grpc::Status errorStatus; if (status.toErrorCode() == RpcInvalidParams) diff --git a/src/xrpld/rpc/handlers/ledger/Ledger.h b/src/xrpld/rpc/handlers/ledger/Ledger.h index 59b64832f7..07d24b497d 100644 --- a/src/xrpld/rpc/handlers/ledger/Ledger.h +++ b/src/xrpld/rpc/handlers/ledger/Ledger.h @@ -18,7 +18,7 @@ namespace json { class Object; } // namespace json -namespace xrpl::RPC { +namespace xrpl::rpc { struct JsonContext; @@ -42,9 +42,9 @@ public: // NOLINTBEGIN(readability-identifier-naming) static constexpr char name[] = "ledger"; - static constexpr unsigned minApiVer = RPC::kApiMinimumSupportedVersion; + static constexpr unsigned minApiVer = rpc::kApiMinimumSupportedVersion; - static constexpr unsigned maxApiVer = RPC::kApiMaximumValidVersion; + static constexpr unsigned maxApiVer = rpc::kApiMaximumValidVersion; static constexpr Role role = Role::USER; @@ -59,4 +59,4 @@ private: int options_ = 0; }; -} // namespace xrpl::RPC +} // namespace xrpl::rpc diff --git a/src/xrpld/rpc/handlers/ledger/LedgerClosed.cpp b/src/xrpld/rpc/handlers/ledger/LedgerClosed.cpp index 7ea314292f..def0a73cd3 100644 --- a/src/xrpld/rpc/handlers/ledger/LedgerClosed.cpp +++ b/src/xrpld/rpc/handlers/ledger/LedgerClosed.cpp @@ -10,7 +10,7 @@ namespace xrpl { json::Value -doLedgerClosed(RPC::JsonContext& context) +doLedgerClosed(rpc::JsonContext& context) { auto ledger = context.ledgerMaster.getClosedLedger(); XRPL_ASSERT(ledger, "xrpl::doLedgerClosed : non-null closed ledger"); diff --git a/src/xrpld/rpc/handlers/ledger/LedgerCurrent.cpp b/src/xrpld/rpc/handlers/ledger/LedgerCurrent.cpp index 1d05774163..ac04084848 100644 --- a/src/xrpld/rpc/handlers/ledger/LedgerCurrent.cpp +++ b/src/xrpld/rpc/handlers/ledger/LedgerCurrent.cpp @@ -8,7 +8,7 @@ namespace xrpl { json::Value -doLedgerCurrent(RPC::JsonContext& context) +doLedgerCurrent(rpc::JsonContext& context) { json::Value jvResult; jvResult[jss::ledger_current_index] = context.ledgerMaster.getCurrentLedgerIndex(); diff --git a/src/xrpld/rpc/handlers/ledger/LedgerData.cpp b/src/xrpld/rpc/handlers/ledger/LedgerData.cpp index 64ab30374b..697d8e52b8 100644 --- a/src/xrpld/rpc/handlers/ledger/LedgerData.cpp +++ b/src/xrpld/rpc/handlers/ledger/LedgerData.cpp @@ -36,12 +36,12 @@ namespace xrpl { // state: array of state nodes // marker: resume point, if any json::Value -doLedgerData(RPC::JsonContext& context) +doLedgerData(rpc::JsonContext& context) { std::shared_ptr lpLedger; auto const& params = context.params; - auto jvResult = RPC::lookupLedger(lpLedger, context); + auto jvResult = rpc::lookupLedger(lpLedger, context); if (!lpLedger) return jvResult; @@ -51,14 +51,14 @@ doLedgerData(RPC::JsonContext& context) { json::Value const& jMarker = params[jss::marker]; if (!(jMarker.isString() && key.parseHex(jMarker.asString()))) - return RPC::expectedFieldError(jss::marker, "valid"); + return rpc::expectedFieldError(jss::marker, "valid"); } bool isBinary = false; if (params.isMember(jss::binary)) { if (!params[jss::binary].isBool()) - return RPC::expectedFieldError(jss::binary, "boolean"); + return rpc::expectedFieldError(jss::binary, "boolean"); isBinary = params[jss::binary].asBool(); } @@ -67,12 +67,12 @@ doLedgerData(RPC::JsonContext& context) { json::Value const& jLimit = params[jss::limit]; if (!jLimit.isIntegral()) - return RPC::expectedFieldError(jss::limit, "integer"); + return rpc::expectedFieldError(jss::limit, "integer"); limit = jLimit.asInt(); } - auto maxLimit = RPC::Tuning::pageLength(isBinary); + auto maxLimit = rpc::tuning::pageLength(isBinary); if ((limit < 0) || ((limit > maxLimit) && (!isUnlimited(context.role)))) limit = maxLimit; @@ -86,7 +86,7 @@ doLedgerData(RPC::JsonContext& context) *lpLedger, &context, isBinary ? static_cast(LedgerFill::Options::Binary) : 0)); } - auto [rpcStatus, type] = RPC::chooseLedgerEntryType(params); + auto [rpcStatus, type] = rpc::chooseLedgerEntryType(params); if (rpcStatus) { jvResult.clear(); @@ -131,14 +131,14 @@ doLedgerData(RPC::JsonContext& context) } std::pair -doLedgerDataGrpc(RPC::GRPCContext& context) +doLedgerDataGrpc(rpc::GRPCContext& context) { org::xrpl::rpc::v1::GetLedgerDataRequest const& request = context.params; org::xrpl::rpc::v1::GetLedgerDataResponse response; grpc::Status const status = grpc::Status::OK; std::shared_ptr ledger; - if (auto status = RPC::ledgerFromRequest(ledger, context)) + if (auto status = rpc::ledgerFromRequest(ledger, context)) { grpc::Status errorStatus; if (status.toErrorCode() == RpcInvalidParams) @@ -177,7 +177,7 @@ doLedgerDataGrpc(RPC::GRPCContext& con e = ledger->sles.upperBound(*key); } - int maxLimit = RPC::Tuning::pageLength(true); + int maxLimit = rpc::tuning::pageLength(true); for (auto i = ledger->sles.upperBound(startKey); i != e; ++i) { diff --git a/src/xrpld/rpc/handlers/ledger/LedgerDiff.cpp b/src/xrpld/rpc/handlers/ledger/LedgerDiff.cpp index f1a9253de2..5e83bedf08 100644 --- a/src/xrpld/rpc/handlers/ledger/LedgerDiff.cpp +++ b/src/xrpld/rpc/handlers/ledger/LedgerDiff.cpp @@ -15,7 +15,7 @@ namespace xrpl { std::pair -doLedgerDiffGrpc(RPC::GRPCContext& context) +doLedgerDiffGrpc(rpc::GRPCContext& context) { org::xrpl::rpc::v1::GetLedgerDiffRequest const& request = context.params; org::xrpl::rpc::v1::GetLedgerDiffResponse response; @@ -24,13 +24,13 @@ doLedgerDiffGrpc(RPC::GRPCContext& con std::shared_ptr baseLedgerRv; std::shared_ptr desiredLedgerRv; - if (RPC::ledgerFromSpecifier(baseLedgerRv, request.base_ledger(), context)) + if (rpc::ledgerFromSpecifier(baseLedgerRv, request.base_ledger(), context)) { grpc::Status const errorStatus{grpc::StatusCode::NOT_FOUND, "base ledger not found"}; return {response, errorStatus}; } - if (RPC::ledgerFromSpecifier(desiredLedgerRv, request.desired_ledger(), context)) + if (rpc::ledgerFromSpecifier(desiredLedgerRv, request.desired_ledger(), context)) { grpc::Status const errorStatus{grpc::StatusCode::NOT_FOUND, "desired ledger not found"}; return {response, errorStatus}; diff --git a/src/xrpld/rpc/handlers/ledger/LedgerEntry.cpp b/src/xrpld/rpc/handlers/ledger/LedgerEntry.cpp index 784be779bb..0dd52b6776 100644 --- a/src/xrpld/rpc/handlers/ledger/LedgerEntry.cpp +++ b/src/xrpld/rpc/handlers/ledger/LedgerEntry.cpp @@ -66,11 +66,11 @@ parseObjectID( json::StaticString const fieldName, std::string const& expectedType = "hex string or object") { - if (auto const uNodeIndex = LedgerEntryHelpers::parse(params)) + if (auto const uNodeIndex = ledger_entry_helpers::parse(params)) { return *uNodeIndex; } - return LedgerEntryHelpers::invalidFieldError("malformedRequest", fieldName, expectedType); + return ledger_entry_helpers::invalidFieldError("malformedRequest", fieldName, expectedType); } static std::expected @@ -101,12 +101,12 @@ parseAccountRoot( json::StaticString const fieldName, [[maybe_unused]] unsigned const apiVersion) { - if (auto const account = LedgerEntryHelpers::parse(params)) + if (auto const account = ledger_entry_helpers::parse(params)) { return keylet::account(*account).key; } - return LedgerEntryHelpers::invalidFieldError("malformedAddress", fieldName, "AccountID"); + return ledger_entry_helpers::invalidFieldError("malformedAddress", fieldName, "AccountID"); } auto const parseAmendments = fixed(keylet::amendments()); @@ -122,17 +122,18 @@ parseAMM( return parseObjectID(params, fieldName); } - if (auto const value = LedgerEntryHelpers::hasRequired(params, {jss::asset, jss::asset2}); + if (auto const value = ledger_entry_helpers::hasRequired(params, {jss::asset, jss::asset2}); !value) { return std::unexpected(value.error()); } - auto const asset = LedgerEntryHelpers::requiredAsset(params, jss::asset, "malformedRequest"); + auto const asset = ledger_entry_helpers::requiredAsset(params, jss::asset, "malformedRequest"); if (!asset) return std::unexpected(asset.error()); - auto const asset2 = LedgerEntryHelpers::requiredAsset(params, jss::asset2, "malformedRequest"); + auto const asset2 = + ledger_entry_helpers::requiredAsset(params, jss::asset2, "malformedRequest"); if (!asset2) return std::unexpected(asset2.error()); @@ -147,7 +148,7 @@ parseBridge( { if (!params.isMember(jss::bridge)) { - return std::unexpected(LedgerEntryHelpers::missingFieldError(jss::bridge)); + return std::unexpected(ledger_entry_helpers::missingFieldError(jss::bridge)); } if (params[jss::bridge].isString()) @@ -155,11 +156,11 @@ parseBridge( return parseObjectID(params, fieldName); } - auto const bridge = LedgerEntryHelpers::parseBridgeFields(params[jss::bridge]); + auto const bridge = ledger_entry_helpers::parseBridgeFields(params[jss::bridge]); if (!bridge) return std::unexpected(bridge.error()); - auto const account = LedgerEntryHelpers::requiredAccountID( + auto const account = ledger_entry_helpers::requiredAccountID( params, jss::bridge_account, "malformedBridgeAccount"); if (!account) return std::unexpected(account.error()); @@ -167,7 +168,7 @@ parseBridge( STXChainBridge::ChainType const chainType = STXChainBridge::srcChain(account.value() == bridge->lockingChainDoor()); if (account.value() != bridge->door(chainType)) - return LedgerEntryHelpers::malformedError("malformedRequest", ""); + return ledger_entry_helpers::malformedError("malformedRequest", ""); return keylet::bridge(*bridge, chainType).key; } @@ -193,16 +194,16 @@ parseCredential( } auto const subject = - LedgerEntryHelpers::requiredAccountID(cred, jss::subject, "malformedRequest"); + ledger_entry_helpers::requiredAccountID(cred, jss::subject, "malformedRequest"); if (!subject) return std::unexpected(subject.error()); auto const issuer = - LedgerEntryHelpers::requiredAccountID(cred, jss::issuer, "malformedRequest"); + ledger_entry_helpers::requiredAccountID(cred, jss::issuer, "malformedRequest"); if (!issuer) return std::unexpected(issuer.error()); - auto const credType = LedgerEntryHelpers::requiredHexBlob( + auto const credType = ledger_entry_helpers::requiredHexBlob( cred, jss::credential_type, kMaxCredentialTypeLength, "malformedRequest"); if (!credType) return std::unexpected(credType.error()); @@ -222,12 +223,12 @@ parseDelegate( } auto const account = - LedgerEntryHelpers::requiredAccountID(params, jss::account, "malformedAddress"); + ledger_entry_helpers::requiredAccountID(params, jss::account, "malformedAddress"); if (!account) return std::unexpected(account.error()); auto const authorize = - LedgerEntryHelpers::requiredAccountID(params, jss::authorize, "malformedAddress"); + ledger_entry_helpers::requiredAccountID(params, jss::authorize, "malformedAddress"); if (!authorize) return std::unexpected(authorize.error()); @@ -239,7 +240,7 @@ parseAuthorizeCredentials(json::Value const& jv) { if (!jv.isArray()) { - return LedgerEntryHelpers::invalidFieldError( + return ledger_entry_helpers::invalidFieldError( "malformedAuthorizedCredentials", jss::authorized_credentials, "array"); } @@ -247,7 +248,7 @@ parseAuthorizeCredentials(json::Value const& jv) if (n > kMaxCredentialsArraySize) { return std::unexpected( - LedgerEntryHelpers::malformedError( + ledger_entry_helpers::malformedError( "malformedAuthorizedCredentials", "Invalid field '" + std::string(jss::authorized_credentials) + "', array too long.")); @@ -256,7 +257,7 @@ parseAuthorizeCredentials(json::Value const& jv) if (n == 0) { return std::unexpected( - LedgerEntryHelpers::malformedError( + ledger_entry_helpers::malformedError( "malformedAuthorizedCredentials", "Invalid field '" + std::string(jss::authorized_credentials) + "', array empty.")); } @@ -266,23 +267,23 @@ parseAuthorizeCredentials(json::Value const& jv) { if (!jo.isObject()) { - return LedgerEntryHelpers::invalidFieldError( + return ledger_entry_helpers::invalidFieldError( "malformedAuthorizedCredentials", jss::authorized_credentials, "array of objects"); } - if (auto const value = LedgerEntryHelpers::hasRequired( + if (auto const value = ledger_entry_helpers::hasRequired( jo, {jss::issuer, jss::credential_type}, "malformedAuthorizedCredentials"); !value) { return std::unexpected(value.error()); } - auto const issuer = LedgerEntryHelpers::requiredAccountID( + auto const issuer = ledger_entry_helpers::requiredAccountID( jo, jss::issuer, "malformedAuthorizedCredentials"); if (!issuer) return std::unexpected(issuer.error()); - auto const credentialType = LedgerEntryHelpers::requiredHexBlob( + auto const credentialType = ledger_entry_helpers::requiredHexBlob( jo, jss::credential_type, kMaxCredentialTypeLength, "malformedAuthorizedCredentials"); if (!credentialType) return std::unexpected(credentialType.error()); @@ -309,13 +310,13 @@ parseDepositPreauth( if ((dp.isMember(jss::authorized) == dp.isMember(jss::authorized_credentials))) { - return LedgerEntryHelpers::malformedError( + return ledger_entry_helpers::malformedError( "malformedRequest", "Must have exactly one of `authorized` and " "`authorized_credentials`."); } - auto const owner = LedgerEntryHelpers::requiredAccountID(dp, jss::owner, "malformedOwner"); + auto const owner = ledger_entry_helpers::requiredAccountID(dp, jss::owner, "malformedOwner"); if (!owner) { return std::unexpected(owner.error()); @@ -323,11 +324,11 @@ parseDepositPreauth( if (dp.isMember(jss::authorized)) { - if (auto const authorized = LedgerEntryHelpers::parse(dp[jss::authorized])) + if (auto const authorized = ledger_entry_helpers::parse(dp[jss::authorized])) { return keylet::depositPreauth(*owner, *authorized).key; } - return LedgerEntryHelpers::invalidFieldError( + return ledger_entry_helpers::invalidFieldError( "malformedAuthorized", jss::authorized, "AccountID"); } @@ -340,7 +341,7 @@ parseDepositPreauth( if (sorted.empty()) { // TODO: this error message is bad/inaccurate - return LedgerEntryHelpers::invalidFieldError( + return ledger_entry_helpers::invalidFieldError( "malformedAuthorizedCredentials", jss::authorized_credentials, "array"); } @@ -353,10 +354,10 @@ parseDID( json::StaticString const fieldName, [[maybe_unused]] unsigned const apiVersion) { - auto const account = LedgerEntryHelpers::parse(params); + auto const account = ledger_entry_helpers::parse(params); if (!account) { - return LedgerEntryHelpers::invalidFieldError("malformedAddress", fieldName, "AccountID"); + return ledger_entry_helpers::invalidFieldError("malformedAddress", fieldName, "AccountID"); } return keylet::did(*account).key; @@ -377,12 +378,13 @@ parseDirectoryNode( (!params[jss::sub_index].isConvertibleTo(json::ValueType::UInt) || params[jss::sub_index].isBool())) { - return LedgerEntryHelpers::invalidFieldError("malformedRequest", jss::sub_index, "number"); + return ledger_entry_helpers::invalidFieldError( + "malformedRequest", jss::sub_index, "number"); } if (params.isMember(jss::owner) == params.isMember(jss::dir_root)) { - return LedgerEntryHelpers::malformedError( + return ledger_entry_helpers::malformedError( "malformedRequest", "Must have exactly one of `owner` and `dir_root` fields."); } @@ -390,27 +392,27 @@ parseDirectoryNode( if (params.isMember(jss::dir_root)) { - if (auto const uDirRoot = LedgerEntryHelpers::parse(params[jss::dir_root])) + if (auto const uDirRoot = ledger_entry_helpers::parse(params[jss::dir_root])) { return keylet::page(*uDirRoot, uSubIndex).key; } - return LedgerEntryHelpers::invalidFieldError("malformedDirRoot", jss::dir_root, "hash"); + return ledger_entry_helpers::invalidFieldError("malformedDirRoot", jss::dir_root, "hash"); } if (params.isMember(jss::owner)) { - auto const ownerID = LedgerEntryHelpers::parse(params[jss::owner]); + auto const ownerID = ledger_entry_helpers::parse(params[jss::owner]); if (!ownerID) { - return LedgerEntryHelpers::invalidFieldError( + return ledger_entry_helpers::invalidFieldError( "malformedAddress", jss::owner, "AccountID"); } return keylet::page(keylet::ownerDir(*ownerID), uSubIndex).key; } - return LedgerEntryHelpers::malformedError("malformedRequest", ""); + return ledger_entry_helpers::malformedError("malformedRequest", ""); } static std::expected @@ -424,10 +426,10 @@ parseEscrow( return parseObjectID(params, fieldName); } - auto const id = LedgerEntryHelpers::requiredAccountID(params, jss::owner, "malformedOwner"); + auto const id = ledger_entry_helpers::requiredAccountID(params, jss::owner, "malformedOwner"); if (!id) return std::unexpected(id.error()); - auto const seq = LedgerEntryHelpers::requiredUInt32(params, jss::seq, "malformedSeq"); + auto const seq = ledger_entry_helpers::requiredUInt32(params, jss::seq, "malformedSeq"); if (!seq) return std::unexpected(seq.error()); @@ -449,7 +451,7 @@ parseFixed( } if (!params.asBool()) { - return LedgerEntryHelpers::invalidFieldError("invalidParams", fieldName, "true"); + return ledger_entry_helpers::invalidFieldError("invalidParams", fieldName, "true"); } return keylet.key; @@ -486,10 +488,10 @@ parseLoanBroker( return parseObjectID(params, fieldName, "hex string"); } - auto const id = LedgerEntryHelpers::requiredAccountID(params, jss::owner, "malformedOwner"); + auto const id = ledger_entry_helpers::requiredAccountID(params, jss::owner, "malformedOwner"); if (!id) return std::unexpected(id.error()); - auto const seq = LedgerEntryHelpers::requiredUInt32(params, jss::seq, "malformedSeq"); + auto const seq = ledger_entry_helpers::requiredUInt32(params, jss::seq, "malformedSeq"); if (!seq) return std::unexpected(seq.error()); @@ -508,10 +510,10 @@ parseLoan( } auto const id = - LedgerEntryHelpers::requiredUInt256(params, jss::loan_broker_id, "malformedBroker"); + ledger_entry_helpers::requiredUInt256(params, jss::loan_broker_id, "malformedBroker"); if (!id) return std::unexpected(id.error()); - auto const seq = LedgerEntryHelpers::requiredUInt32(params, jss::loan_seq, "malformedSeq"); + auto const seq = ledger_entry_helpers::requiredUInt32(params, jss::loan_seq, "malformedSeq"); if (!seq) return std::unexpected(seq.error()); @@ -529,13 +531,13 @@ parseMPToken( return parseObjectID(params, fieldName); } - auto const mptIssuanceID = - LedgerEntryHelpers::requiredUInt192(params, jss::mpt_issuance_id, "malformedMPTIssuanceID"); + auto const mptIssuanceID = ledger_entry_helpers::requiredUInt192( + params, jss::mpt_issuance_id, "malformedMPTIssuanceID"); if (!mptIssuanceID) return std::unexpected(mptIssuanceID.error()); auto const account = - LedgerEntryHelpers::requiredAccountID(params, jss::account, "malformedAccount"); + ledger_entry_helpers::requiredAccountID(params, jss::account, "malformedAccount"); if (!account) return std::unexpected(account.error()); @@ -548,10 +550,10 @@ parseMPTokenIssuance( json::StaticString const fieldName, [[maybe_unused]] unsigned const apiVersion) { - auto const mptIssuanceID = LedgerEntryHelpers::parse(params); + auto const mptIssuanceID = ledger_entry_helpers::parse(params); if (!mptIssuanceID) { - return LedgerEntryHelpers::invalidFieldError( + return ledger_entry_helpers::invalidFieldError( "malformedMPTokenIssuance", fieldName, "Hash192"); } @@ -589,11 +591,12 @@ parseOffer( return parseObjectID(params, fieldName); } - auto const id = LedgerEntryHelpers::requiredAccountID(params, jss::account, "malformedAddress"); + auto const id = + ledger_entry_helpers::requiredAccountID(params, jss::account, "malformedAddress"); if (!id) return std::unexpected(id.error()); - auto const seq = LedgerEntryHelpers::requiredUInt32(params, jss::seq, "malformedRequest"); + auto const seq = ledger_entry_helpers::requiredUInt32(params, jss::seq, "malformedRequest"); if (!seq) return std::unexpected(seq.error()); @@ -611,12 +614,13 @@ parseOracle( return parseObjectID(params, fieldName); } - auto const id = LedgerEntryHelpers::requiredAccountID(params, jss::account, "malformedAccount"); + auto const id = + ledger_entry_helpers::requiredAccountID(params, jss::account, "malformedAccount"); if (!id) return std::unexpected(id.error()); - auto const seq = - LedgerEntryHelpers::requiredUInt32(params, jss::oracle_document_id, "malformedDocumentID"); + auto const seq = ledger_entry_helpers::requiredUInt32( + params, jss::oracle_document_id, "malformedDocumentID"); if (!seq) return std::unexpected(seq.error()); @@ -645,16 +649,16 @@ parsePermissionedDomain( if (!pd.isObject()) { - return LedgerEntryHelpers::invalidFieldError( + return ledger_entry_helpers::invalidFieldError( "malformedRequest", fieldName, "hex string or object"); } auto const account = - LedgerEntryHelpers::requiredAccountID(pd, jss::account, "malformedAddress"); + ledger_entry_helpers::requiredAccountID(pd, jss::account, "malformedAddress"); if (!account) return std::unexpected(account.error()); - auto const seq = LedgerEntryHelpers::requiredUInt32(pd, jss::seq, "malformedRequest"); + auto const seq = ledger_entry_helpers::requiredUInt32(pd, jss::seq, "malformedRequest"); if (!seq) return std::unexpected(seq.error()); @@ -675,7 +679,7 @@ parseRippleState( } if (auto const value = - LedgerEntryHelpers::hasRequired(jvRippleState, {jss::currency, jss::accounts}); + ledger_entry_helpers::hasRequired(jvRippleState, {jss::currency, jss::accounts}); !value) { return std::unexpected(value.error()); @@ -683,27 +687,27 @@ parseRippleState( if (!jvRippleState[jss::accounts].isArray() || jvRippleState[jss::accounts].size() != 2) { - return LedgerEntryHelpers::invalidFieldError( + return ledger_entry_helpers::invalidFieldError( "malformedRequest", jss::accounts, "length-2 array of Accounts"); } - auto const id1 = LedgerEntryHelpers::parse(jvRippleState[jss::accounts][0u]); - auto const id2 = LedgerEntryHelpers::parse(jvRippleState[jss::accounts][1u]); + auto const id1 = ledger_entry_helpers::parse(jvRippleState[jss::accounts][0u]); + auto const id2 = ledger_entry_helpers::parse(jvRippleState[jss::accounts][1u]); if (!id1 || !id2) { - return LedgerEntryHelpers::invalidFieldError( + return ledger_entry_helpers::invalidFieldError( "malformedAddress", jss::accounts, "array of Accounts"); } if (id1 == id2) { - return LedgerEntryHelpers::malformedError( + return ledger_entry_helpers::malformedError( "malformedRequest", "Cannot have a trustline to self."); } if (!jvRippleState[jss::currency].isString() || jvRippleState[jss::currency] == "" || !toCurrency(uCurrency, jvRippleState[jss::currency].asString())) { - return LedgerEntryHelpers::invalidFieldError( + return ledger_entry_helpers::invalidFieldError( "malformedCurrency", jss::currency, "Currency"); } @@ -729,12 +733,12 @@ parseSponsorship( return parseObjectID(params, fieldName); auto const sponsorID = - LedgerEntryHelpers::requiredAccountID(params, jss::sponsor, "malformedSponsor"); + ledger_entry_helpers::requiredAccountID(params, jss::sponsor, "malformedSponsor"); if (!sponsorID) return std::unexpected(sponsorID.error()); auto const sponseeID = - LedgerEntryHelpers::requiredAccountID(params, jss::sponsee, "malformedSponsee"); + ledger_entry_helpers::requiredAccountID(params, jss::sponsee, "malformedSponsee"); if (!sponseeID) return std::unexpected(sponseeID.error()); @@ -752,12 +756,13 @@ parseTicket( return parseObjectID(params, fieldName); } - auto const id = LedgerEntryHelpers::requiredAccountID(params, jss::account, "malformedAddress"); + auto const id = + ledger_entry_helpers::requiredAccountID(params, jss::account, "malformedAddress"); if (!id) return std::unexpected(id.error()); auto const seq = - LedgerEntryHelpers::requiredUInt32(params, jss::ticket_seq, "malformedRequest"); + ledger_entry_helpers::requiredUInt32(params, jss::ticket_seq, "malformedRequest"); if (!seq) return std::unexpected(seq.error()); @@ -775,11 +780,11 @@ parseVault( return parseObjectID(params, fieldName); } - auto const id = LedgerEntryHelpers::requiredAccountID(params, jss::owner, "malformedOwner"); + auto const id = ledger_entry_helpers::requiredAccountID(params, jss::owner, "malformedOwner"); if (!id) return std::unexpected(id.error()); - auto const seq = LedgerEntryHelpers::requiredUInt32(params, jss::seq, "malformedRequest"); + auto const seq = ledger_entry_helpers::requiredUInt32(params, jss::seq, "malformedRequest"); if (!seq) return std::unexpected(seq.error()); @@ -797,11 +802,11 @@ parseXChainOwnedClaimID( return parseObjectID(claimId, fieldName); } - auto const bridgeSpec = LedgerEntryHelpers::parseBridgeFields(claimId); + auto const bridgeSpec = ledger_entry_helpers::parseBridgeFields(claimId); if (!bridgeSpec) return std::unexpected(bridgeSpec.error()); - auto const seq = LedgerEntryHelpers::requiredUInt32( + auto const seq = ledger_entry_helpers::requiredUInt32( claimId, jss::xchain_owned_claim_id, "malformedXChainOwnedClaimID"); if (!seq) { @@ -823,11 +828,11 @@ parseXChainOwnedCreateAccountClaimID( return parseObjectID(claimId, fieldName); } - auto const bridgeSpec = LedgerEntryHelpers::parseBridgeFields(claimId); + auto const bridgeSpec = ledger_entry_helpers::parseBridgeFields(claimId); if (!bridgeSpec) return std::unexpected(bridgeSpec.error()); - auto const seq = LedgerEntryHelpers::requiredUInt32( + auto const seq = ledger_entry_helpers::requiredUInt32( claimId, jss::xchain_owned_create_account_claim_id, "malformedXChainOwnedCreateAccountClaimID"); @@ -853,7 +858,7 @@ struct LedgerEntry // ... // } json::Value -doLedgerEntry(RPC::JsonContext& context) +doLedgerEntry(rpc::JsonContext& context) { static auto kLedgerEntryParsers = std::to_array({ #pragma push_macro("LEDGER_ENTRY") @@ -892,11 +897,11 @@ doLedgerEntry(RPC::JsonContext& context) if (hasMoreThanOneMember) { - return RPC::makeParamError("Too many fields provided."); + return rpc::makeParamError("Too many fields provided."); } std::shared_ptr lpLedger; - auto jvResult = RPC::lookupLedger(lpLedger, context); + auto jvResult = rpc::lookupLedger(lpLedger, context); if (!lpLedger) return jvResult; @@ -936,7 +941,7 @@ doLedgerEntry(RPC::JsonContext& context) jvResult[jss::error] = "unknownOption"; return jvResult; } - return RPC::makeParamError("No ledger_entry params provided."); + return rpc::makeParamError("No ledger_entry params provided."); } } catch (json::Error const& e) @@ -945,7 +950,7 @@ doLedgerEntry(RPC::JsonContext& context) { // For apiVersion 2 onwards, any parsing failures that throw // this exception return an invalidParam error. - return RPC::makeError(RpcInvalidParams); + return rpc::makeError(RpcInvalidParams); } throw; @@ -956,7 +961,7 @@ doLedgerEntry(RPC::JsonContext& context) if (uNodeIndex.isZero()) { - RPC::injectError(RpcEntryNotFound, jvResult); + rpc::injectError(RpcEntryNotFound, jvResult); return jvResult; } @@ -969,13 +974,13 @@ doLedgerEntry(RPC::JsonContext& context) if (!sleNode) { // Not found. - RPC::injectError(RpcEntryNotFound, jvResult); + rpc::injectError(RpcEntryNotFound, jvResult); return jvResult; } if ((expectedType != ltANY) && (expectedType != sleNode->getType())) { - RPC::injectError(RpcUnexpectedLedgerType, jvResult); + rpc::injectError(RpcUnexpectedLedgerType, jvResult); return jvResult; } @@ -996,14 +1001,14 @@ doLedgerEntry(RPC::JsonContext& context) } std::pair -doLedgerEntryGrpc(RPC::GRPCContext& context) +doLedgerEntryGrpc(rpc::GRPCContext& context) { org::xrpl::rpc::v1::GetLedgerEntryRequest const& request = context.params; org::xrpl::rpc::v1::GetLedgerEntryResponse response; grpc::Status const status = grpc::Status::OK; std::shared_ptr ledger; - if (auto status = RPC::ledgerFromRequest(ledger, context)) + if (auto status = rpc::ledgerFromRequest(ledger, context)) { grpc::Status errorStatus; if (status.toErrorCode() == RpcInvalidParams) diff --git a/src/xrpld/rpc/handlers/ledger/LedgerEntryHelpers.h b/src/xrpld/rpc/handlers/ledger/LedgerEntryHelpers.h index 57c4e58242..1b119db04e 100644 --- a/src/xrpld/rpc/handlers/ledger/LedgerEntryHelpers.h +++ b/src/xrpld/rpc/handlers/ledger/LedgerEntryHelpers.h @@ -22,7 +22,7 @@ #include #include -namespace xrpl::LedgerEntryHelpers { +namespace xrpl::ledger_entry_helpers { inline std::unexpected missingFieldError(json::StaticString const field, std::optional err = std::nullopt) @@ -30,7 +30,7 @@ missingFieldError(json::StaticString const field, std::optional err json::Value json = json::ValueType::Object; json[jss::error] = err.value_or("malformedRequest"); json[jss::error_code] = RpcInvalidParams; - json[jss::error_message] = RPC::missingFieldMessage(std::string(field.cStr())); + json[jss::error_message] = rpc::missingFieldMessage(std::string(field.cStr())); return std::unexpected(json); } @@ -40,7 +40,7 @@ invalidFieldError(std::string const& err, json::StaticString const field, std::s json::Value json = json::ValueType::Object; json[jss::error] = err; json[jss::error_code] = RpcInvalidParams; - json[jss::error_message] = RPC::expectedFieldMessage(field, type); + json[jss::error_message] = rpc::expectedFieldMessage(field, type); return std::unexpected(json); } @@ -291,4 +291,4 @@ parseBridgeFields(json::Value const& params) *lockingChainDoor, lockingChainIssue, *issuingChainDoor, issuingChainIssue); } -} // namespace xrpl::LedgerEntryHelpers +} // namespace xrpl::ledger_entry_helpers diff --git a/src/xrpld/rpc/handlers/ledger/LedgerHeader.cpp b/src/xrpld/rpc/handlers/ledger/LedgerHeader.cpp index e2cb80615b..ec3c9fe602 100644 --- a/src/xrpld/rpc/handlers/ledger/LedgerHeader.cpp +++ b/src/xrpld/rpc/handlers/ledger/LedgerHeader.cpp @@ -18,10 +18,10 @@ namespace xrpl { // ledger_index : // } json::Value -doLedgerHeader(RPC::JsonContext& context) +doLedgerHeader(rpc::JsonContext& context) { std::shared_ptr lpLedger; - auto jvResult = RPC::lookupLedger(lpLedger, context); + auto jvResult = rpc::lookupLedger(lpLedger, context); if (!lpLedger) return jvResult; diff --git a/src/xrpld/rpc/handlers/orderbook/AMMInfo.cpp b/src/xrpld/rpc/handlers/orderbook/AMMInfo.cpp index 7f54f81423..e95c51c483 100644 --- a/src/xrpld/rpc/handlers/orderbook/AMMInfo.cpp +++ b/src/xrpld/rpc/handlers/orderbook/AMMInfo.cpp @@ -61,13 +61,13 @@ toIso8601(NetClock::time_point tp) } json::Value -doAMMInfo(RPC::JsonContext& context) +doAMMInfo(rpc::JsonContext& context) { auto const& params(context.params); json::Value result; std::shared_ptr ledger; - result = RPC::lookupLedger(ledger, context); + result = rpc::lookupLedger(ledger, context); if (!ledger) return result; @@ -174,7 +174,7 @@ doAMMInfo(RPC::JsonContext& context) auto const r = getValuesFromContextParams(); if (!r) { - RPC::injectError(r.error(), result); + rpc::injectError(r.error(), result); return result; } diff --git a/src/xrpld/rpc/handlers/orderbook/BookChanges.cpp b/src/xrpld/rpc/handlers/orderbook/BookChanges.cpp index abad196246..0f796df3a4 100644 --- a/src/xrpld/rpc/handlers/orderbook/BookChanges.cpp +++ b/src/xrpld/rpc/handlers/orderbook/BookChanges.cpp @@ -11,15 +11,15 @@ namespace xrpl { json::Value -doBookChanges(RPC::JsonContext& context) +doBookChanges(rpc::JsonContext& context) { std::shared_ptr ledger; - json::Value result = RPC::lookupLedger(ledger, context); + json::Value result = rpc::lookupLedger(ledger, context); if (ledger == nullptr) return result; - return RPC::computeBookChanges(ledger); + return rpc::computeBookChanges(ledger); } } // namespace xrpl diff --git a/src/xrpld/rpc/handlers/orderbook/BookOffers.cpp b/src/xrpld/rpc/handlers/orderbook/BookOffers.cpp index 63dee76f1b..ae539a59f3 100644 --- a/src/xrpld/rpc/handlers/orderbook/BookOffers.cpp +++ b/src/xrpld/rpc/handlers/orderbook/BookOffers.cpp @@ -32,19 +32,19 @@ validateTakerJSON(json::Value const& taker, json::StaticString const& name) { if (!taker.isMember(jss::currency) && !taker.isMember(jss::mpt_issuance_id)) { - return RPC::missingFieldError((boost::format("%s.currency") % name.cStr()).str()); + return rpc::missingFieldError((boost::format("%s.currency") % name.cStr()).str()); } if (taker.isMember(jss::mpt_issuance_id) && (taker.isMember(jss::currency) || taker.isMember(jss::issuer))) { - return RPC::invalidFieldError(name.cStr()); + return rpc::invalidFieldError(name.cStr()); } if ((taker.isMember(jss::currency) && !taker[jss::currency].isString()) || (taker.isMember(jss::mpt_issuance_id) && !taker[jss::mpt_issuance_id].isString())) { - return RPC::expectedFieldError( + return rpc::expectedFieldError( (boost::format("%s.currency") % name.cStr()).str(), "string"); } @@ -71,7 +71,7 @@ parseTakerAssetJSON( if (!toCurrency(issue.currency, taker[jss::currency].asString())) { JLOG(j.info()) << boost::format("Bad %s currency.") % name.cStr(); - return RPC::makeError( + return rpc::makeError( assetError, (boost::format("Invalid field '%s.currency', bad currency.") % name.cStr()).str()); } @@ -82,7 +82,7 @@ parseTakerAssetJSON( MPTID mptid; if (!mptid.parseHex(taker[jss::mpt_issuance_id].asString())) { - return RPC::makeError( + return rpc::makeError( assetError, (boost::format("Invalid field '%s.mpt_issuance_id'") % name.cStr()).str()); } @@ -113,20 +113,20 @@ parseTakerIssuerJSON( { if (!taker[jss::issuer].isString()) { - return RPC::expectedFieldError( + return rpc::expectedFieldError( (boost::format("%s.issuer") % name.cStr()).str(), "string"); } if (!toIssuer(issue.account, taker[jss::issuer].asString())) { - return RPC::makeError( + return rpc::makeError( issuerError, (boost::format("Invalid field '%s.issuer', bad issuer.") % name.cStr()).str()); } if (issue.account == noAccount()) { - return RPC::makeError( + return rpc::makeError( issuerError, (boost::format("Invalid field '%s.issuer', bad issuer account one.") % name.cStr()) @@ -140,7 +140,7 @@ parseTakerIssuerJSON( if (isXRP(issue.currency) && !isXRP(issue.account)) { - return RPC::makeError( + return rpc::makeError( issuerError, (boost::format( "Unneeded field '%s.issuer' for XRP currency " @@ -151,7 +151,7 @@ parseTakerIssuerJSON( if (!isXRP(issue.currency) && isXRP(issue.account)) { - return RPC::makeError( + return rpc::makeError( issuerError, (boost::format("Invalid field '%s.issuer', expected non-XRP issuer.") % name.cStr()) .str()); @@ -162,7 +162,7 @@ parseTakerIssuerJSON( } json::Value -doBookOffers(RPC::JsonContext& context) +doBookOffers(rpc::JsonContext& context) { // VFALCO TODO Here is a terrible place for this kind of business // logic. It needs to be moved elsewhere and documented, @@ -171,25 +171,25 @@ doBookOffers(RPC::JsonContext& context) return rpcError(RpcTooBusy); std::shared_ptr lpLedger; - auto jvResult = RPC::lookupLedger(lpLedger, context); + auto jvResult = rpc::lookupLedger(lpLedger, context); if (!lpLedger) return jvResult; if (!context.params.isMember(jss::taker_pays)) - return RPC::missingFieldError(jss::taker_pays); + return rpc::missingFieldError(jss::taker_pays); if (!context.params.isMember(jss::taker_gets)) - return RPC::missingFieldError(jss::taker_gets); + return rpc::missingFieldError(jss::taker_gets); json::Value const& takerPays = context.params[jss::taker_pays]; json::Value const& takerGets = context.params[jss::taker_gets]; if (!takerPays.isObjectOrNull()) - return RPC::objectFieldError(jss::taker_pays); + return rpc::objectFieldError(jss::taker_pays); if (!takerGets.isObjectOrNull()) - return RPC::objectFieldError(jss::taker_gets); + return rpc::objectFieldError(jss::taker_gets); if (auto const err = validateTakerJSON(takerPays, jss::taker_pays)) return *err; @@ -215,11 +215,11 @@ doBookOffers(RPC::JsonContext& context) if (context.params.isMember(jss::taker)) { if (!context.params[jss::taker].isString()) - return RPC::expectedFieldError(jss::taker, "string"); + return rpc::expectedFieldError(jss::taker, "string"); takerID = parseBase58(context.params[jss::taker].asString()); if (!takerID) - return RPC::invalidFieldError(jss::taker); + return rpc::invalidFieldError(jss::taker); } std::optional domain; @@ -229,7 +229,7 @@ doBookOffers(RPC::JsonContext& context) if (!context.params[jss::domain].isString() || !num.parseHex(context.params[jss::domain].asString())) { - return RPC::makeError(RpcDomainMalformed, "Unable to parse domain."); + return rpc::makeError(RpcDomainMalformed, "Unable to parse domain."); } domain = num; @@ -238,11 +238,11 @@ doBookOffers(RPC::JsonContext& context) if (book.in == book.out) { JLOG(context.j.info()) << "taker_gets same as taker_pays."; - return RPC::makeError(RpcBadMarket); + return rpc::makeError(RpcBadMarket); } unsigned int limit = 0; - if (auto err = readLimitField(limit, RPC::Tuning::kBookOffers, context)) + if (auto err = readLimitField(limit, rpc::tuning::kBookOffers, context)) return *err; bool const bProof(context.params.isMember(jss::proof)); @@ -260,7 +260,7 @@ doBookOffers(RPC::JsonContext& context) jvMarker, jvResult); - context.loadType = Resource::kFeeMediumBurdenRpc; + context.loadType = resource::kFeeMediumBurdenRpc; return jvResult; } diff --git a/src/xrpld/rpc/handlers/orderbook/DepositAuthorized.cpp b/src/xrpld/rpc/handlers/orderbook/DepositAuthorized.cpp index 343d539277..9d109fe16f 100644 --- a/src/xrpld/rpc/handlers/orderbook/DepositAuthorized.cpp +++ b/src/xrpld/rpc/handlers/orderbook/DepositAuthorized.cpp @@ -32,17 +32,17 @@ namespace xrpl { // } json::Value -doDepositAuthorized(RPC::JsonContext& context) +doDepositAuthorized(rpc::JsonContext& context) { json::Value const& params = context.params; // Validate source_account. if (!params.isMember(jss::source_account)) - return RPC::missingFieldError(jss::source_account); + return rpc::missingFieldError(jss::source_account); if (!params[jss::source_account].isString()) { - return RPC::makeError( - RpcInvalidParams, RPC::expectedFieldMessage(jss::source_account, "a string")); + return rpc::makeError( + RpcInvalidParams, rpc::expectedFieldMessage(jss::source_account, "a string")); } auto srcID = parseBase58(params[jss::source_account].asString()); @@ -52,11 +52,11 @@ doDepositAuthorized(RPC::JsonContext& context) // Validate destination_account. if (!params.isMember(jss::destination_account)) - return RPC::missingFieldError(jss::destination_account); + return rpc::missingFieldError(jss::destination_account); if (!params[jss::destination_account].isString()) { - return RPC::makeError( - RpcInvalidParams, RPC::expectedFieldMessage(jss::destination_account, "a string")); + return rpc::makeError( + RpcInvalidParams, rpc::expectedFieldMessage(jss::destination_account, "a string")); } auto dstID = parseBase58(params[jss::destination_account].asString()); @@ -66,7 +66,7 @@ doDepositAuthorized(RPC::JsonContext& context) // Validate ledger. std::shared_ptr ledger; - json::Value result = RPC::lookupLedger(ledger, context); + json::Value result = rpc::lookupLedger(ledger, context); if (!ledger) return result; @@ -74,7 +74,7 @@ doDepositAuthorized(RPC::JsonContext& context) // If source account is not in the ledger it can't be authorized. if (!ledger->exists(keylet::account(srcAcct))) { - RPC::injectError(RpcSrcActNotFound, result); + rpc::injectError(RpcSrcActNotFound, result); return result; } @@ -82,7 +82,7 @@ doDepositAuthorized(RPC::JsonContext& context) auto const sleDest = ledger->read(keylet::account(dstAcct)); if (!sleDest) { - RPC::injectError(RpcDstActNotFound, result); + rpc::injectError(RpcDstActNotFound, result); return result; } @@ -96,15 +96,15 @@ doDepositAuthorized(RPC::JsonContext& context) auto const& creds(params[jss::credentials]); if (!creds.isArray() || !creds) { - return RPC::makeError( + return rpc::makeError( RpcInvalidParams, - RPC::expectedFieldMessage( + rpc::expectedFieldMessage( jss::credentials, "is non-empty array of CredentialID(hash256)")); } if (creds.size() > kMaxCredentialsArraySize) { - return RPC::makeError( - RpcInvalidParams, RPC::expectedFieldMessage(jss::credentials, "array too long")); + return rpc::makeError( + RpcInvalidParams, rpc::expectedFieldMessage(jss::credentials, "array too long")); } lifeExtender.reserve(creds.size()); @@ -112,9 +112,9 @@ doDepositAuthorized(RPC::JsonContext& context) { if (!jo.isString()) { - return RPC::makeError( + return rpc::makeError( RpcInvalidParams, - RPC::expectedFieldMessage( + rpc::expectedFieldMessage( jss::credentials, "an array of CredentialID(hash256)")); } @@ -122,34 +122,34 @@ doDepositAuthorized(RPC::JsonContext& context) auto const credS = jo.asString(); if (!credH.parseHex(credS)) { - return RPC::makeError( + return rpc::makeError( RpcInvalidParams, - RPC::expectedFieldMessage( + rpc::expectedFieldMessage( jss::credentials, "an array of CredentialID(hash256)")); } SLE::const_pointer sleCred = ledger->read(keylet::credential(credH)); if (!sleCred) { - RPC::injectError(RpcBadCredentials, "credentials don't exist", result); + rpc::injectError(RpcBadCredentials, "credentials don't exist", result); return result; } if (!sleCred->isFlag(lsfAccepted)) { - RPC::injectError(RpcBadCredentials, "credentials aren't accepted", result); + rpc::injectError(RpcBadCredentials, "credentials aren't accepted", result); return result; } if (credentials::checkExpired(*sleCred, ledger->header().parentCloseTime)) { - RPC::injectError(RpcBadCredentials, "credentials are expired", result); + rpc::injectError(RpcBadCredentials, "credentials are expired", result); return result; } if ((*sleCred)[sfSubject] != srcAcct) { - RPC::injectError( + rpc::injectError( RpcBadCredentials, "credentials doesn't belong to the root account", result); return result; } @@ -157,7 +157,7 @@ doDepositAuthorized(RPC::JsonContext& context) auto [it, ins] = sorted.emplace((*sleCred)[sfIssuer], (*sleCred)[sfCredentialType]); if (!ins) { - RPC::injectError(RpcBadCredentials, "duplicates in credentials", result); + rpc::injectError(RpcBadCredentials, "duplicates in credentials", result); return result; } lifeExtender.push_back(std::move(sleCred)); diff --git a/src/xrpld/rpc/handlers/orderbook/GetAggregatePrice.cpp b/src/xrpld/rpc/handlers/orderbook/GetAggregatePrice.cpp index 632456a3fa..f493000d0b 100644 --- a/src/xrpld/rpc/handlers/orderbook/GetAggregatePrice.cpp +++ b/src/xrpld/rpc/handlers/orderbook/GetAggregatePrice.cpp @@ -48,7 +48,7 @@ using Prices = bimap>, multiset_of const& f) { @@ -149,26 +149,26 @@ getStats(Prices::right_const_iterator const& begin, Prices::right_const_iterator * range - {most recent, most recent - time_threshold} [optional] */ json::Value -doGetAggregatePrice(RPC::JsonContext& context) +doGetAggregatePrice(rpc::JsonContext& context) { json::Value result; auto const& params(context.params); static constexpr std::uint16_t kMaxOracles = 200; if (!params.isMember(jss::oracles)) - return RPC::missingFieldError(jss::oracles); + return rpc::missingFieldError(jss::oracles); if (!params[jss::oracles].isArray() || params[jss::oracles].size() == 0 || params[jss::oracles].size() > kMaxOracles) { - RPC::injectError(RpcOracleMalformed, result); + rpc::injectError(RpcOracleMalformed, result); return result; } if (!params.isMember(jss::base_asset)) - return RPC::missingFieldError(jss::base_asset); + return rpc::missingFieldError(jss::base_asset); if (!params.isMember(jss::quote_asset)) - return RPC::missingFieldError(jss::quote_asset); + return rpc::missingFieldError(jss::quote_asset); // Lambda to validate uint type // support positive int, uint, and a number represented as a string @@ -213,38 +213,38 @@ doGetAggregatePrice(RPC::JsonContext& context) auto const trim = getField(jss::trim); if (std::holds_alternative(trim)) { - RPC::injectError(std::get(trim), result); + rpc::injectError(std::get(trim), result); return result; } if (params.isMember(jss::trim) && (std::get(trim) == 0 || std::get(trim) > kMaxTrim)) { - RPC::injectError(RpcInvalidParams, result); + rpc::injectError(RpcInvalidParams, result); return result; } auto const timeThreshold = getField(jss::time_threshold, 0); if (std::holds_alternative(timeThreshold)) { - RPC::injectError(std::get(timeThreshold), result); + rpc::injectError(std::get(timeThreshold), result); return result; } auto const baseAsset = getCurrency(sfBaseAsset, jss::base_asset); if (std::holds_alternative(baseAsset)) { - RPC::injectError(std::get(baseAsset), result); + rpc::injectError(std::get(baseAsset), result); return result; } auto const quoteAsset = getCurrency(sfQuoteAsset, jss::quote_asset); if (std::holds_alternative(quoteAsset)) { - RPC::injectError(std::get(quoteAsset), result); + rpc::injectError(std::get(quoteAsset), result); return result; } std::shared_ptr ledger; - result = RPC::lookupLedger(ledger, context); + result = rpc::lookupLedger(ledger, context); if (!ledger) return result; // LCOV_EXCL_LINE @@ -255,7 +255,7 @@ doGetAggregatePrice(RPC::JsonContext& context) { if (!oracle.isMember(jss::oracle_document_id) || !oracle.isMember(jss::account)) { - RPC::injectError(RpcOracleMalformed, result); + rpc::injectError(RpcOracleMalformed, result); return result; } auto const documentID = validUInt(oracle, jss::oracle_document_id) @@ -264,7 +264,7 @@ doGetAggregatePrice(RPC::JsonContext& context) auto const account = parseBase58(oracle[jss::account].asString()); if (!account || account->isZero() || !documentID) { - RPC::injectError(RpcInvalidParams, result); + rpc::injectError(RpcInvalidParams, result); return result; } @@ -298,7 +298,7 @@ doGetAggregatePrice(RPC::JsonContext& context) if (prices.empty()) { - RPC::injectError(RpcObjectNotFound, result); + rpc::injectError(RpcObjectNotFound, result); return result; } @@ -321,7 +321,7 @@ doGetAggregatePrice(RPC::JsonContext& context) if (prices.empty()) { // LCOV_EXCL_START - RPC::injectError(RpcInternal, result); + rpc::injectError(RpcInternal, result); return result; // LCOV_EXCL_STOP } diff --git a/src/xrpld/rpc/handlers/orderbook/NFTBuyOffers.cpp b/src/xrpld/rpc/handlers/orderbook/NFTBuyOffers.cpp index 88e4392dad..60b48ac5a6 100644 --- a/src/xrpld/rpc/handlers/orderbook/NFTBuyOffers.cpp +++ b/src/xrpld/rpc/handlers/orderbook/NFTBuyOffers.cpp @@ -10,15 +10,15 @@ namespace xrpl { json::Value -doNFTBuyOffers(RPC::JsonContext& context) +doNFTBuyOffers(rpc::JsonContext& context) { if (!context.params.isMember(jss::nft_id)) - return RPC::missingFieldError(jss::nft_id); + return rpc::missingFieldError(jss::nft_id); uint256 nftId; if (!nftId.parseHex(context.params[jss::nft_id].asString())) - return RPC::invalidFieldError(jss::nft_id); + return rpc::invalidFieldError(jss::nft_id); return enumerateNFTOffers(context, nftId, keylet::nftBuys(nftId)); } diff --git a/src/xrpld/rpc/handlers/orderbook/NFTOffersHelpers.h b/src/xrpld/rpc/handlers/orderbook/NFTOffersHelpers.h index 70bc258d77..e03830ae0d 100644 --- a/src/xrpld/rpc/handlers/orderbook/NFTOffersHelpers.h +++ b/src/xrpld/rpc/handlers/orderbook/NFTOffersHelpers.h @@ -52,15 +52,15 @@ appendNftOfferJson(Application const& app, SLE::const_ref offer, json::Value& of // marker: opaque // optional, resume previous query // } inline json::Value -enumerateNFTOffers(RPC::JsonContext& context, uint256 const& nftId, Keylet const& directory) +enumerateNFTOffers(rpc::JsonContext& context, uint256 const& nftId, Keylet const& directory) { unsigned int limit = 0; - if (auto err = readLimitField(limit, RPC::Tuning::kNftOffers, context)) + if (auto err = readLimitField(limit, rpc::tuning::kNftOffers, context)) return *err; std::shared_ptr ledger; - if (auto result = RPC::lookupLedger(ledger, context); !ledger) + if (auto result = rpc::lookupLedger(ledger, context); !ledger) return result; if (!ledger->exists(directory)) @@ -83,7 +83,7 @@ enumerateNFTOffers(RPC::JsonContext& context, uint256 const& nftId, Keylet const json::Value const& marker(context.params[jss::marker]); if (!marker.isString()) - return RPC::expectedFieldError(jss::marker, "string"); + return rpc::expectedFieldError(jss::marker, "string"); if (!startAfter.parseHex(marker.asString())) return rpcError(RpcInvalidParams); @@ -127,7 +127,7 @@ enumerateNFTOffers(RPC::JsonContext& context, uint256 const& nftId, Keylet const for (auto const& offer : offers) appendNftOfferJson(context.app, offer, jsonOffers); - context.loadType = Resource::kFeeMediumBurdenRpc; + context.loadType = resource::kFeeMediumBurdenRpc; return result; } diff --git a/src/xrpld/rpc/handlers/orderbook/NFTSellOffers.cpp b/src/xrpld/rpc/handlers/orderbook/NFTSellOffers.cpp index 309df93605..8b09b42a34 100644 --- a/src/xrpld/rpc/handlers/orderbook/NFTSellOffers.cpp +++ b/src/xrpld/rpc/handlers/orderbook/NFTSellOffers.cpp @@ -10,15 +10,15 @@ namespace xrpl { json::Value -doNFTSellOffers(RPC::JsonContext& context) +doNFTSellOffers(rpc::JsonContext& context) { if (!context.params.isMember(jss::nft_id)) - return RPC::missingFieldError(jss::nft_id); + return rpc::missingFieldError(jss::nft_id); uint256 nftId; if (!nftId.parseHex(context.params[jss::nft_id].asString())) - return RPC::invalidFieldError(jss::nft_id); + return rpc::invalidFieldError(jss::nft_id); return enumerateNFTOffers(context, nftId, keylet::nftSells(nftId)); } diff --git a/src/xrpld/rpc/handlers/orderbook/PathFind.cpp b/src/xrpld/rpc/handlers/orderbook/PathFind.cpp index 3a6b52ee98..c46238a5a5 100644 --- a/src/xrpld/rpc/handlers/orderbook/PathFind.cpp +++ b/src/xrpld/rpc/handlers/orderbook/PathFind.cpp @@ -13,7 +13,7 @@ namespace xrpl { json::Value -doPathFind(RPC::JsonContext& context) +doPathFind(rpc::JsonContext& context) { if (context.app.config().pathSearchMax == 0) return rpcError(RpcNotSupported); @@ -34,7 +34,7 @@ doPathFind(RPC::JsonContext& context) if (sSubCommand == "create") { - context.loadType = Resource::kFeeHeavyBurdenRpc; + context.loadType = resource::kFeeHeavyBurdenRpc; context.infoSub->clearRequest(); return context.app.getPathRequestManager().makePathRequest( context.infoSub, lpLedger, context.params); diff --git a/src/xrpld/rpc/handlers/orderbook/RipplePathFind.cpp b/src/xrpld/rpc/handlers/orderbook/RipplePathFind.cpp index b7edfb6dbe..923cb0f7f5 100644 --- a/src/xrpld/rpc/handlers/orderbook/RipplePathFind.cpp +++ b/src/xrpld/rpc/handlers/orderbook/RipplePathFind.cpp @@ -21,12 +21,12 @@ namespace xrpl { // This interface is deprecated. json::Value -doRipplePathFind(RPC::JsonContext& context) +doRipplePathFind(rpc::JsonContext& context) { if (context.app.config().pathSearchMax == 0) return rpcError(RpcNotSupported); - context.loadType = Resource::kFeeHeavyBurdenRpc; + context.loadType = resource::kFeeHeavyBurdenRpc; std::shared_ptr lpLedger; json::Value jvResult; @@ -37,7 +37,7 @@ doRipplePathFind(RPC::JsonContext& context) // No ledger specified, use pathfinding defaults // and dispatch to pathfinding engine if (context.app.getLedgerMaster().getValidatedLedgerAge() > - RPC::Tuning::kMaxValidatedLedgerAge) + rpc::tuning::kMaxValidatedLedgerAge) { if (context.apiVersion == 1) return rpcError(RpcNoNetwork); @@ -146,11 +146,11 @@ doRipplePathFind(RPC::JsonContext& context) } // The caller specified a ledger - jvResult = RPC::lookupLedger(lpLedger, context); + jvResult = rpc::lookupLedger(lpLedger, context); if (!lpLedger) return jvResult; - RPC::LegacyPathFind const lpf(isUnlimited(context.role), context.app); + rpc::LegacyPathFind const lpf(isUnlimited(context.role), context.app); if (!lpf.isOk()) return rpcError(RpcTooBusy); diff --git a/src/xrpld/rpc/handlers/server_info/Feature.cpp b/src/xrpld/rpc/handlers/server_info/Feature.cpp index bd7198b61c..1906658106 100644 --- a/src/xrpld/rpc/handlers/server_info/Feature.cpp +++ b/src/xrpld/rpc/handlers/server_info/Feature.cpp @@ -18,7 +18,7 @@ namespace xrpl { // vetoed : true/false // } json::Value -doFeature(RPC::JsonContext& context) +doFeature(rpc::JsonContext& context) { if (context.params.isMember(jss::feature)) { diff --git a/src/xrpld/rpc/handlers/server_info/Fee.cpp b/src/xrpld/rpc/handlers/server_info/Fee.cpp index 1fe5476d50..2e4147fc1e 100644 --- a/src/xrpld/rpc/handlers/server_info/Fee.cpp +++ b/src/xrpld/rpc/handlers/server_info/Fee.cpp @@ -8,7 +8,7 @@ namespace xrpl { json::Value -doFee(RPC::JsonContext& context) +doFee(rpc::JsonContext& context) { auto result = context.app.getTxQ().doRPC(context.app); if (result.type() == json::ValueType::Object) @@ -16,7 +16,7 @@ doFee(RPC::JsonContext& context) // LCOV_EXCL_START UNREACHABLE("xrpl::doFee : invalid result type"); - RPC::injectError(RpcInternal, context.params); + rpc::injectError(RpcInternal, context.params); return context.params; // LCOV_EXCL_STOP } diff --git a/src/xrpld/rpc/handlers/server_info/Manifest.cpp b/src/xrpld/rpc/handlers/server_info/Manifest.cpp index cb1771750b..c0b29d8275 100644 --- a/src/xrpld/rpc/handlers/server_info/Manifest.cpp +++ b/src/xrpld/rpc/handlers/server_info/Manifest.cpp @@ -12,12 +12,12 @@ namespace xrpl { json::Value -doManifest(RPC::JsonContext& context) +doManifest(rpc::JsonContext& context) { auto& params = context.params; if (!params.isMember(jss::public_key)) - return RPC::missingFieldError(jss::public_key); + return rpc::missingFieldError(jss::public_key); auto const requested = params[jss::public_key].asString(); @@ -27,7 +27,7 @@ doManifest(RPC::JsonContext& context) auto const pk = parseBase58(TokenType::NodePublic, requested); if (!pk) { - RPC::injectError(RpcInvalidParams, ret); + rpc::injectError(RpcInvalidParams, ret); return ret; } diff --git a/src/xrpld/rpc/handlers/server_info/ServerDefinitions.cpp b/src/xrpld/rpc/handlers/server_info/ServerDefinitions.cpp index cce1b3e07f..b561ce6d38 100644 --- a/src/xrpld/rpc/handlers/server_info/ServerDefinitions.cpp +++ b/src/xrpld/rpc/handlers/server_info/ServerDefinitions.cpp @@ -382,7 +382,7 @@ getServerDefinitionsJson() } json::Value -doServerDefinitions(RPC::JsonContext& context) +doServerDefinitions(rpc::JsonContext& context) { auto& params = context.params; @@ -390,7 +390,7 @@ doServerDefinitions(RPC::JsonContext& context) if (params.isMember(jss::hash)) { if (!params[jss::hash].isString() || !hash.parseHex(params[jss::hash].asString())) - return RPC::invalidFieldError(jss::hash); + return rpc::invalidFieldError(jss::hash); } auto const& defs = detail::getDefinitions(); diff --git a/src/xrpld/rpc/handlers/server_info/ServerInfo.cpp b/src/xrpld/rpc/handlers/server_info/ServerInfo.cpp index aaad9d2b02..fd6e2f717f 100644 --- a/src/xrpld/rpc/handlers/server_info/ServerInfo.cpp +++ b/src/xrpld/rpc/handlers/server_info/ServerInfo.cpp @@ -9,7 +9,7 @@ namespace xrpl { json::Value -doServerInfo(RPC::JsonContext& context) +doServerInfo(rpc::JsonContext& context) { json::Value ret(json::ValueType::Object); diff --git a/src/xrpld/rpc/handlers/server_info/ServerState.cpp b/src/xrpld/rpc/handlers/server_info/ServerState.cpp index acf4e9eb43..e0d43d4053 100644 --- a/src/xrpld/rpc/handlers/server_info/ServerState.cpp +++ b/src/xrpld/rpc/handlers/server_info/ServerState.cpp @@ -8,7 +8,7 @@ namespace xrpl { json::Value -doServerState(RPC::JsonContext& context) +doServerState(rpc::JsonContext& context) { json::Value ret(json::ValueType::Object); diff --git a/src/xrpld/rpc/handlers/server_info/Version.h b/src/xrpld/rpc/handlers/server_info/Version.h index f25d8679ba..40ad4e5e71 100644 --- a/src/xrpld/rpc/handlers/server_info/Version.h +++ b/src/xrpld/rpc/handlers/server_info/Version.h @@ -9,7 +9,7 @@ #include #include -namespace xrpl::RPC { +namespace xrpl::rpc { class VersionHandler { @@ -34,9 +34,9 @@ public: // NOLINTBEGIN(readability-identifier-naming) static constexpr char const* name = "version"; - static constexpr unsigned minApiVer = RPC::kApiMinimumSupportedVersion; + static constexpr unsigned minApiVer = rpc::kApiMinimumSupportedVersion; - static constexpr unsigned maxApiVer = RPC::kApiMaximumValidVersion; + static constexpr unsigned maxApiVer = rpc::kApiMaximumValidVersion; static constexpr Role role = Role::USER; @@ -48,4 +48,4 @@ private: bool betaEnabled_; }; -} // namespace xrpl::RPC +} // namespace xrpl::rpc diff --git a/src/xrpld/rpc/handlers/subscribe/Subscribe.cpp b/src/xrpld/rpc/handlers/subscribe/Subscribe.cpp index 93840bb6d6..7ce432c49e 100644 --- a/src/xrpld/rpc/handlers/subscribe/Subscribe.cpp +++ b/src/xrpld/rpc/handlers/subscribe/Subscribe.cpp @@ -27,7 +27,7 @@ namespace xrpl { json::Value -doSubscribe(RPC::JsonContext& context) +doSubscribe(rpc::JsonContext& context) { InfoSub::pointer ispSub; json::Value jvResult(json::ValueType::Object); @@ -79,7 +79,7 @@ doSubscribe(RPC::JsonContext& context) } catch (std::runtime_error const& ex) { - return RPC::makeParamError(ex.what()); + return rpc::makeParamError(ex.what()); } } else @@ -174,7 +174,7 @@ doSubscribe(RPC::JsonContext& context) if (!context.params[accountsProposed].isArray()) return rpcError(RpcInvalidParams); - auto ids = RPC::parseAccountIds(context.params[accountsProposed]); + auto ids = rpc::parseAccountIds(context.params[accountsProposed]); if (ids.empty()) return rpcError(RpcActMalformed); context.netOps.subAccount(ispSub, ids, true); @@ -185,7 +185,7 @@ doSubscribe(RPC::JsonContext& context) if (!context.params[jss::accounts].isArray()) return rpcError(RpcInvalidParams); - auto ids = RPC::parseAccountIds(context.params[jss::accounts]); + auto ids = rpc::parseAccountIds(context.params[jss::accounts]); if (ids.empty()) return rpcError(RpcActMalformed); context.netOps.subAccount(ispSub, ids, false); @@ -197,7 +197,7 @@ doSubscribe(RPC::JsonContext& context) if (!context.app.config().useTxTables()) return rpcError(RpcNotEnabled); - context.loadType = Resource::kFeeMediumBurdenRpc; + context.loadType = resource::kFeeMediumBurdenRpc; auto const& req = context.params[jss::account_history_tx_stream]; if (!req.isMember(jss::account) || !req[jss::account].isString()) return rpcError(RpcInvalidParams); @@ -230,11 +230,11 @@ doSubscribe(RPC::JsonContext& context) Book book; - if (auto const err = RPC::parseSubUnsubJson(book.in, j, jss::taker_pays, context.j); + if (auto const err = rpc::parseSubUnsubJson(book.in, j, jss::taker_pays, context.j); err != RpcSuccess) return rpcError(err); - if (auto const err = RPC::parseSubUnsubJson(book.out, j, jss::taker_gets, context.j); + if (auto const err = rpc::parseSubUnsubJson(book.out, j, jss::taker_gets, context.j); err != RpcSuccess) return rpcError(err); @@ -285,7 +285,7 @@ doSubscribe(RPC::JsonContext& context) if ((j.isMember(jss::snapshot) && j[jss::snapshot].asBool()) || (j.isMember(jss::state_now) && j[jss::state_now].asBool())) { - context.loadType = Resource::kFeeMediumBurdenRpc; + context.loadType = resource::kFeeMediumBurdenRpc; std::shared_ptr lpLedger = context.app.getLedgerMaster().getPublishedLedger(); if (lpLedger) @@ -299,7 +299,7 @@ doSubscribe(RPC::JsonContext& context) field == jss::asks ? reversed(book) : book, takerID ? *takerID : noAccount(), false, - RPC::Tuning::kBookOffers.rDefault, + rpc::tuning::kBookOffers.rDefault, jvMarker, jvOffers); diff --git a/src/xrpld/rpc/handlers/subscribe/Unsubscribe.cpp b/src/xrpld/rpc/handlers/subscribe/Unsubscribe.cpp index af42af2a55..33c785a9bb 100644 --- a/src/xrpld/rpc/handlers/subscribe/Unsubscribe.cpp +++ b/src/xrpld/rpc/handlers/subscribe/Unsubscribe.cpp @@ -18,7 +18,7 @@ namespace xrpl { json::Value -doUnsubscribe(RPC::JsonContext& context) +doUnsubscribe(rpc::JsonContext& context) { InfoSub::pointer ispSub; json::Value jvResult(json::ValueType::Object); @@ -106,7 +106,7 @@ doUnsubscribe(RPC::JsonContext& context) if (!context.params[accountsProposed].isArray()) return rpcError(RpcInvalidParams); - auto ids = RPC::parseAccountIds(context.params[accountsProposed]); + auto ids = rpc::parseAccountIds(context.params[accountsProposed]); if (ids.empty()) return rpcError(RpcActMalformed); context.netOps.unsubAccount(ispSub, ids, true); @@ -117,7 +117,7 @@ doUnsubscribe(RPC::JsonContext& context) if (!context.params[jss::accounts].isArray()) return rpcError(RpcInvalidParams); - auto ids = RPC::parseAccountIds(context.params[jss::accounts]); + auto ids = rpc::parseAccountIds(context.params[jss::accounts]); if (ids.empty()) return rpcError(RpcActMalformed); context.netOps.unsubAccount(ispSub, ids, false); @@ -161,11 +161,11 @@ doUnsubscribe(RPC::JsonContext& context) Book book; - if (auto const err = RPC::parseSubUnsubJson(book.in, jv, jss::taker_pays, context.j); + if (auto const err = rpc::parseSubUnsubJson(book.in, jv, jss::taker_pays, context.j); err != RpcSuccess) return rpcError(err); - if (auto const err = RPC::parseSubUnsubJson(book.out, jv, jss::taker_gets, context.j); + if (auto const err = rpc::parseSubUnsubJson(book.out, jv, jss::taker_gets, context.j); err != RpcSuccess) return rpcError(err); diff --git a/src/xrpld/rpc/handlers/transaction/Simulate.cpp b/src/xrpld/rpc/handlers/transaction/Simulate.cpp index 0f163c7356..8441add08b 100644 --- a/src/xrpld/rpc/handlers/transaction/Simulate.cpp +++ b/src/xrpld/rpc/handlers/transaction/Simulate.cpp @@ -44,7 +44,7 @@ namespace xrpl { static std::expected -getAutofillSequence(json::Value const& txJson, RPC::JsonContext& context) +getAutofillSequence(json::Value const& txJson, rpc::JsonContext& context) { // autofill Sequence bool const hasTicketSeq = txJson.isMember(sfTicketSequence.jsonName); @@ -53,14 +53,14 @@ getAutofillSequence(json::Value const& txJson, RPC::JsonContext& context) { // sanity check, should fail earlier // LCOV_EXCL_START - return std::unexpected(RPC::invalidFieldError("tx.Account")); + return std::unexpected(rpc::invalidFieldError("tx.Account")); // LCOV_EXCL_STOP } auto const srcAddressID = parseBase58(accountStr.asString()); if (!srcAddressID.has_value()) { return std::unexpected( - RPC::makeError(RpcSrcActMalformed, RPC::invalidFieldMessage("tx.Account"))); + rpc::makeError(RpcSrcActMalformed, rpc::invalidFieldMessage("tx.Account"))); } SLE::const_pointer const sle = context.app.getOpenLedger().current()->read(keylet::account(*srcAddressID)); @@ -88,7 +88,7 @@ autofillSignature(json::Value& sigObject, std::string const& fieldPrefix = "tx") if (sigObject.isMember(jss::Signers)) { if (!sigObject[jss::Signers].isArray()) - return RPC::invalidFieldError(fieldPrefix + ".Signers"); + return rpc::invalidFieldError(fieldPrefix + ".Signers"); // check multisigned signers for (unsigned index = 0; index < sigObject[jss::Signers].size(); index++) { @@ -96,7 +96,7 @@ autofillSignature(json::Value& sigObject, std::string const& fieldPrefix = "tx") if (!signer.isObject() || !signer.isMember(jss::Signer) || !signer[jss::Signer].isObject()) { - return RPC::invalidFieldError( + return rpc::invalidFieldError( fieldPrefix + ".Signers[" + std::to_string(index) + "]"); } @@ -133,7 +133,7 @@ autofillSignature(json::Value& sigObject, std::string const& fieldPrefix = "tx") } static std::optional -autofillTx(json::Value& txJson, RPC::JsonContext& context) +autofillTx(json::Value& txJson, rpc::JsonContext& context) { if (auto error = autofillSignature(txJson)) return error; @@ -142,7 +142,7 @@ autofillTx(json::Value& txJson, RPC::JsonContext& context) { auto& sponsorSignature = txJson[sfSponsorSignature.jsonName]; if (!sponsorSignature.isObject()) - return RPC::objectFieldError(sfSponsorSignature.jsonName); + return rpc::objectFieldError(sfSponsorSignature.jsonName); if (auto const error = autofillSignature(sponsorSignature, "tx.SponsorSignature")) return error; @@ -167,7 +167,7 @@ autofillTx(json::Value& txJson, RPC::JsonContext& context) { // Autofill Fee after normalizing nested signer fields so the fee // estimator sees the full transaction shape. - auto feeOrError = RPC::getCurrentNetworkFee( + auto feeOrError = rpc::getCurrentNetworkFee( context.role, context.app.config(), context.app.getFeeTrack(), @@ -191,18 +191,18 @@ getTxJsonFromParams(json::Value const& params) { if (params.isMember(jss::tx_json)) { - return RPC::makeParamError("Can only include one of `tx_blob` and `tx_json`."); + return rpc::makeParamError("Can only include one of `tx_blob` and `tx_json`."); } auto const txBlob = params[jss::tx_blob]; if (!txBlob.isString()) { - return RPC::invalidFieldError(jss::tx_blob); + return rpc::invalidFieldError(jss::tx_blob); } auto unHexed = strUnHex(txBlob.asString()); if (!unHexed || unHexed->empty()) - return RPC::invalidFieldError(jss::tx_blob); + return rpc::invalidFieldError(jss::tx_blob); try { @@ -211,7 +211,7 @@ getTxJsonFromParams(json::Value const& params) } catch (std::runtime_error const&) { - return RPC::invalidFieldError(jss::tx_blob); + return rpc::invalidFieldError(jss::tx_blob); } } else if (params.isMember(jss::tx_json)) @@ -219,30 +219,30 @@ getTxJsonFromParams(json::Value const& params) txJson = params[jss::tx_json]; if (!txJson.isObject()) { - return RPC::objectFieldError(jss::tx_json); + return rpc::objectFieldError(jss::tx_json); } } else { - return RPC::makeParamError("Neither `tx_blob` nor `tx_json` included."); + return rpc::makeParamError("Neither `tx_blob` nor `tx_json` included."); } // basic sanity checks for transaction shape if (!txJson.isMember(jss::TransactionType)) { - return RPC::missingFieldError("tx.TransactionType"); + return rpc::missingFieldError("tx.TransactionType"); } if (!txJson.isMember(jss::Account)) { - return RPC::missingFieldError("tx.Account"); + return rpc::missingFieldError("tx.Account"); } return txJson; } static json::Value -simulateTxn(RPC::JsonContext& context, std::shared_ptr transaction) +simulateTxn(rpc::JsonContext& context, std::shared_ptr transaction) { json::Value jvResult; // Process the transaction @@ -290,11 +290,11 @@ simulateTxn(RPC::JsonContext& context, std::shared_ptr transaction) else { jvResult[jss::meta] = result.metadata->getJson(JsonOptions::Values::None); - RPC::insertDeliveredAmount( + rpc::insertDeliveredAmount( jvResult[jss::meta], view, transaction->getSTransaction(), *result.metadata); - RPC::insertNFTSyntheticInJson( + rpc::insertNFTSyntheticInJson( jvResult, transaction->getSTransaction(), *result.metadata); - RPC::insertMPTokenIssuanceID( + rpc::insertMPTokenIssuanceID( jvResult[jss::meta], transaction->getSTransaction(), *result.metadata); } } @@ -317,23 +317,23 @@ simulateTxn(RPC::JsonContext& context, std::shared_ptr transaction) // binary: // } json::Value -doSimulate(RPC::JsonContext& context) +doSimulate(rpc::JsonContext& context) { - context.loadType = Resource::kFeeMediumBurdenRpc; + context.loadType = resource::kFeeMediumBurdenRpc; json::Value txJson; // the tx as a JSON // check validity of `binary` param if (context.params.isMember(jss::binary) && !context.params[jss::binary].isBool()) { - return RPC::invalidFieldError(jss::binary); + return rpc::invalidFieldError(jss::binary); } for (auto const field : {jss::secret, jss::seed, jss::seed_hex, jss::passphrase}) { if (context.params.isMember(field)) { - return RPC::invalidFieldError(field); + return rpc::invalidFieldError(field); } } @@ -365,13 +365,13 @@ doSimulate(RPC::JsonContext& context) if (stTx->getTxnType() == ttBATCH) { - return RPC::makeError(RpcNotImpl); + return rpc::makeError(RpcNotImpl); } // Reject transactions with the tfInnerBatchTxn flag. if (stTx->isFlag(tfInnerBatchTxn)) { - return RPC::makeError( + return rpc::makeError( RpcInvalidParams, "tfInnerBatchTxn flag is not allowed on top-level transactions."); } diff --git a/src/xrpld/rpc/handlers/transaction/Submit.cpp b/src/xrpld/rpc/handlers/transaction/Submit.cpp index 79f3680684..05a1552221 100644 --- a/src/xrpld/rpc/handlers/transaction/Submit.cpp +++ b/src/xrpld/rpc/handlers/transaction/Submit.cpp @@ -27,11 +27,11 @@ namespace xrpl { static std::expected -getFailHard(RPC::JsonContext const& context) +getFailHard(rpc::JsonContext const& context) { if (context.params.isMember(jss::fail_hard) && !context.params[jss::fail_hard].isBool()) { - return std::unexpected(RPC::expectedFieldError(jss::fail_hard, "boolean")); + return std::unexpected(rpc::expectedFieldError(jss::fail_hard, "boolean")); } return NetworkOPs::doFailHard( context.params.isMember(jss::fail_hard) && context.params[jss::fail_hard].asBool()); @@ -42,9 +42,9 @@ getFailHard(RPC::JsonContext const& context) // secret: // } json::Value -doSubmit(RPC::JsonContext& context) +doSubmit(rpc::JsonContext& context) { - context.loadType = Resource::kFeeMediumBurdenRpc; + context.loadType = resource::kFeeMediumBurdenRpc; if (!context.params.isMember(jss::tx_blob)) { @@ -53,16 +53,16 @@ doSubmit(RPC::JsonContext& context) return failType.error(); if (context.role != Role::ADMIN && !context.app.config().canSign()) - return RPC::makeError(RpcNotSupported, "Signing is not supported by this server."); + return rpc::makeError(RpcNotSupported, "Signing is not supported by this server."); - auto ret = RPC::transactionSubmit( + auto ret = rpc::transactionSubmit( context.params, context.apiVersion, *failType, context.role, context.ledgerMaster.getValidatedLedgerAge(), context.app, - RPC::getProcessTxnFn(context.netOps)); + rpc::getProcessTxnFn(context.netOps)); ret[jss::deprecated] = "Signing support in the 'submit' command has been " diff --git a/src/xrpld/rpc/handlers/transaction/SubmitMultiSigned.cpp b/src/xrpld/rpc/handlers/transaction/SubmitMultiSigned.cpp index cc04ed073e..09301ca8a6 100644 --- a/src/xrpld/rpc/handlers/transaction/SubmitMultiSigned.cpp +++ b/src/xrpld/rpc/handlers/transaction/SubmitMultiSigned.cpp @@ -13,20 +13,20 @@ namespace xrpl { // tx_json: , // } json::Value -doSubmitMultiSigned(RPC::JsonContext& context) +doSubmitMultiSigned(rpc::JsonContext& context) { - context.loadType = Resource::kFeeHeavyBurdenRpc; + context.loadType = resource::kFeeHeavyBurdenRpc; auto const failHard = context.params[jss::fail_hard].asBool(); auto const failType = NetworkOPs::doFailHard(failHard); - return RPC::transactionSubmitMultiSigned( + return rpc::transactionSubmitMultiSigned( context.params, context.apiVersion, failType, context.role, context.ledgerMaster.getValidatedLedgerAge(), context.app, - RPC::getProcessTxnFn(context.netOps)); + rpc::getProcessTxnFn(context.netOps)); } } // namespace xrpl diff --git a/src/xrpld/rpc/handlers/transaction/TransactionEntry.cpp b/src/xrpld/rpc/handlers/transaction/TransactionEntry.cpp index bb68226334..2bd97e852f 100644 --- a/src/xrpld/rpc/handlers/transaction/TransactionEntry.cpp +++ b/src/xrpld/rpc/handlers/transaction/TransactionEntry.cpp @@ -21,10 +21,10 @@ namespace xrpl { // XXX In this case, not specify either ledger does not mean ledger current. It // means any ledger. json::Value -doTransactionEntry(RPC::JsonContext& context) +doTransactionEntry(rpc::JsonContext& context) { std::shared_ptr lpLedger; - json::Value jvResult = RPC::lookupLedger(lpLedger, context); + json::Value jvResult = rpc::lookupLedger(lpLedger, context); if (!lpLedger) return jvResult; @@ -84,7 +84,7 @@ doTransactionEntry(RPC::JsonContext& context) jvResult[jss::tx_json] = sttx->getJson(JsonOptions::Values::None); } - RPC::insertDeliverMax(jvResult[jss::tx_json], sttx->getTxnType(), context.apiVersion); + rpc::insertDeliverMax(jvResult[jss::tx_json], sttx->getTxnType(), context.apiVersion); auto const jsonMeta = (context.apiVersion > 1 ? jss::meta : jss::metadata); if (stobj) diff --git a/src/xrpld/rpc/handlers/transaction/Tx.cpp b/src/xrpld/rpc/handlers/transaction/Tx.cpp index c065bc268e..ee7110bf6b 100644 --- a/src/xrpld/rpc/handlers/transaction/Tx.cpp +++ b/src/xrpld/rpc/handlers/transaction/Tx.cpp @@ -68,8 +68,8 @@ struct TxArgs std::optional> ledgerRange; }; -std::pair -doTxHelp(RPC::Context& context, TxArgs args) +std::pair +doTxHelp(rpc::Context& context, TxArgs args) { TxResult result; @@ -169,7 +169,7 @@ doTxHelp(RPC::Context& context, TxArgs args) uint32_t const netID = context.app.getNetworkIDService().getNetworkID(); if (txnIdx <= 0xFFFFU && netID < 0xFFFFU && lgrSeq < 0x0FFF'FFFFUL) - result.ctid = RPC::encodeCTID(lgrSeq, txnIdx, netID); + result.ctid = rpc::encodeCTID(lgrSeq, txnIdx, netID); } } @@ -178,12 +178,12 @@ doTxHelp(RPC::Context& context, TxArgs args) json::Value populateJsonResponse( - std::pair const& res, + std::pair const& res, TxArgs const& args, - RPC::JsonContext const& context) + rpc::JsonContext const& context) { json::Value response; - RPC::Status const& error = res.second; + rpc::Status const& error = res.second; TxResult const& result = res.first; // handle errors if (error.toErrorCode() != RpcSuccess) @@ -215,7 +215,7 @@ populateJsonResponse( else { response[jss::tx_json] = result.txn->getJson(kOptionsJson); - RPC::insertDeliverMax( + rpc::insertDeliverMax( response[jss::tx_json], sttx->getTxnType(), context.apiVersion); } @@ -236,7 +236,7 @@ populateJsonResponse( { response = result.txn->getJson(JsonOptions::Values::IncludeDate, args.binary); if (!args.binary) - RPC::insertDeliverMax(response, sttx->getTxnType(), context.apiVersion); + rpc::insertDeliverMax(response, sttx->getTxnType(), context.apiVersion); } // populate binary metadata @@ -254,8 +254,8 @@ populateJsonResponse( { response[jss::meta] = meta->getJson(JsonOptions::Values::None); insertDeliveredAmount(response[jss::meta], context, result.txn, *meta); - RPC::insertNFTSyntheticInJson(response, sttx, *meta); - RPC::insertMPTokenIssuanceID(response[jss::meta], sttx, *meta); + rpc::insertNFTSyntheticInJson(response, sttx, *meta); + rpc::insertMPTokenIssuanceID(response[jss::meta], sttx, *meta); } } response[jss::validated] = result.validated; @@ -267,7 +267,7 @@ populateJsonResponse( } json::Value -doTxJson(RPC::JsonContext& context) +doTxJson(rpc::JsonContext& context) { if (!context.app.config().useTxTables()) return rpcError(RpcNotEnabled); @@ -291,7 +291,7 @@ doTxJson(RPC::JsonContext& context) } else if (context.params.isMember(jss::ctid)) { - auto ctid = RPC::decodeCTID(context.params[jss::ctid].asString()); + auto ctid = rpc::decodeCTID(context.params[jss::ctid].asString()); if (!ctid) return rpcError(RpcInvalidParams); @@ -302,7 +302,7 @@ doTxJson(RPC::JsonContext& context) out << "Wrong network. You should submit this request to a node " "running on NetworkID: " << net_id; - return RPC::makeError(RpcWrongNetwork, out.str()); + return rpc::makeError(RpcWrongNetwork, out.str()); } args.ctid = {lgr_seq, txn_idx}; } @@ -327,7 +327,7 @@ doTxJson(RPC::JsonContext& context) } } - std::pair const res = doTxHelp(context, args); + std::pair const res = doTxHelp(context, args); return populateJsonResponse(res, args, context); } diff --git a/src/xrpld/rpc/handlers/transaction/TxHistory.cpp b/src/xrpld/rpc/handlers/transaction/TxHistory.cpp index a45046773c..2d5ad8cbe5 100644 --- a/src/xrpld/rpc/handlers/transaction/TxHistory.cpp +++ b/src/xrpld/rpc/handlers/transaction/TxHistory.cpp @@ -16,12 +16,12 @@ namespace xrpl { // start: // } json::Value -doTxHistory(RPC::JsonContext& context) +doTxHistory(rpc::JsonContext& context) { if (!context.app.config().useTxTables()) return rpcError(RpcNotEnabled); - context.loadType = Resource::kFeeMediumBurdenRpc; + context.loadType = resource::kFeeMediumBurdenRpc; if (!context.params.isMember(jss::start)) return rpcError(RpcInvalidParams); @@ -40,7 +40,7 @@ doTxHistory(RPC::JsonContext& context) for (auto const& t : trans) { json::Value txJson = t->getJson(JsonOptions::Values::None); - RPC::insertDeliverMax(txJson, t->getSTransaction()->getTxnType(), context.apiVersion); + rpc::insertDeliverMax(txJson, t->getSTransaction()->getTxnType(), context.apiVersion); txs.append(txJson); } diff --git a/src/xrpld/rpc/handlers/transaction/TxReduceRelay.cpp b/src/xrpld/rpc/handlers/transaction/TxReduceRelay.cpp index edc4eff057..8956603012 100644 --- a/src/xrpld/rpc/handlers/transaction/TxReduceRelay.cpp +++ b/src/xrpld/rpc/handlers/transaction/TxReduceRelay.cpp @@ -7,7 +7,7 @@ namespace xrpl { json::Value -doTxReduceRelay(RPC::JsonContext& context) +doTxReduceRelay(rpc::JsonContext& context) { return context.app.getOverlay().txMetrics(); } diff --git a/src/xrpld/rpc/handlers/utility/Ping.cpp b/src/xrpld/rpc/handlers/utility/Ping.cpp index 0d34b9e0fc..68fb06456e 100644 --- a/src/xrpld/rpc/handlers/utility/Ping.cpp +++ b/src/xrpld/rpc/handlers/utility/Ping.cpp @@ -6,12 +6,12 @@ namespace xrpl { -namespace RPC { +namespace rpc { struct JsonContext; -} // namespace RPC +} // namespace rpc json::Value -doPing(RPC::JsonContext& context) +doPing(rpc::JsonContext& context) { json::Value ret(json::ValueType::Object); switch (context.role) diff --git a/src/xrpld/rpc/handlers/utility/Random.cpp b/src/xrpld/rpc/handlers/utility/Random.cpp index 56df442cf1..d3428ee6e9 100644 --- a/src/xrpld/rpc/handlers/utility/Random.cpp +++ b/src/xrpld/rpc/handlers/utility/Random.cpp @@ -10,16 +10,16 @@ namespace xrpl { -namespace RPC { +namespace rpc { struct JsonContext; -} // namespace RPC +} // namespace rpc // Result: // { // random: // } json::Value -doRandom(RPC::JsonContext& context) +doRandom(rpc::JsonContext& context) { // TODO(tom): the try/catch is almost certainly redundant, we catch at the // top level too. diff --git a/tests/conan/src/example.cpp b/tests/conan/src/example.cpp index acfb253a7d..4720af4384 100644 --- a/tests/conan/src/example.cpp +++ b/tests/conan/src/example.cpp @@ -5,6 +5,6 @@ int main(int argc, char const** argv) { - std::printf("%s\n", xrpl::BuildInfo::getVersionString().c_str()); + std::printf("%s\n", xrpl::build_info::getVersionString().c_str()); return 0; } From e0de716ee6e01f9542f24c8738de2edd9d3055b3 Mon Sep 17 00:00:00 2001 From: Vito Tumas <5780819+Tapanito@users.noreply.github.com> Date: Tue, 4 Aug 2026 15:49:18 +0200 Subject: [PATCH 12/32] fix: Watch nix/*.nix files for direnv cache invalidation (#7948) --- .envrc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.envrc b/.envrc index 3550a30f2d..cecf4b4767 100644 --- a/.envrc +++ b/.envrc @@ -1 +1,3 @@ +watch_file nix/*.nix + use flake From ebe54d9db5fa4aa28c0eb9a6cd8f55ded0b5144c Mon Sep 17 00:00:00 2001 From: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> Date: Tue, 4 Aug 2026 15:12:40 +0100 Subject: [PATCH 13/32] fix(telemetry): correct two alert thresholds found by 7-day backtest ValidatedLedgerStale fired on every node, healthy included. LedgerMaster::getValidatedLedgerAge() returns weeks{2} (1209600s) as a SENTINEL when no validated ledger exists, not as a measurement. The rule read that as "14 days stale". Measured over six days it produced sustained firing on all nine nodes. Excluding the exact sentinel value drops that to zero while still tracking real staleness. ManifestFloodInbound at 50 kB/s was routine paging: ~41 sustained 5-minute samples across six healthy nodes in six days. Healthy p99 is 1.0-1.8 kB/s and real storms peak at 2.7 MB/s, so 512 kB/s sits ~280x above normal and ~5x below the peaks, cutting sustained samples to 2. Both thresholds were previously justified from a 24-hour window, which was too short to expose either problem. --- .../grafana/provisioning/alerting/rules.yaml | 39 ++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/docker/telemetry/grafana/provisioning/alerting/rules.yaml b/docker/telemetry/grafana/provisioning/alerting/rules.yaml index d8a2803c9f..a66e1fcceb 100644 --- a/docker/telemetry/grafana/provisioning/alerting/rules.yaml +++ b/docker/telemetry/grafana/provisioning/alerting/rules.yaml @@ -181,8 +181,18 @@ groups: # The validated ledger falling behind wall-clock is the single clearest # "this node is unhealthy" signal on XRPL: it is the symptom every other - # consensus/sync failure eventually produces. Measured p95 is 4s on every - # node over 24h, so 60s carries ~15x headroom. + # consensus/sync failure eventually produces. Measured p50 2s / p95 4s / + # p99 5s across all nodes over 7d, so 60s carries ~12x headroom over p99. + # + # The `< 1209600` clause is REQUIRED, not defensive. When there is no + # validated ledger at all, LedgerMaster::getValidatedLedgerAge() returns + # weeks{2} == 1209600s as a SENTINEL rather than a measurement + # (LedgerMaster.cpp, "No validated ledger" branch). Without the clause the + # rule reads that sentinel as "14 days stale" and fires on every node + # during startup — measured: it produced sustained firing on all 9 nodes + # including healthy ones over a 6-day window. Excluding the exact sentinel + # keeps the rule measuring real staleness; a node genuinely holding no + # validated ledger is caught by LedgerCloseStalled and NodeNotFull. - uid: xrpld-validated-ledger-stale title: ValidatedLedgerStale condition: C @@ -207,7 +217,7 @@ groups: datasourceUid: prometheus model: refId: A - expr: max by (service_instance_id) (ledgermaster_validated_ledger_age{service_name="xrpld"}) + expr: max by (service_instance_id) (ledgermaster_validated_ledger_age{service_name="xrpld"} < 1209600) instant: true range: false intervalMs: 1000 @@ -831,11 +841,20 @@ groups: # catches the wire-level cause (a peer shipping huge dumps) even when the # job pool absorbs it without a visible backlog. # - # Measured: steady state 0.7-2.3 kB/s; p99 during the startup flood - # 267-420 kB/s. 50 kB/s sits ~20x above steady state and well below the - # flood. The uptime gate suppresses the measured startup storm, which is - # normal behaviour — the trade-off is that a flood confined to the first - # 30 minutes after boot is deliberately not alerted. + # Threshold derived from a 7-day sample (uptime-gated), NOT from the + # 24h window that an earlier revision used: + # healthy p95 0.2-0.5 kB/s, p99 1.0-1.8 kB/s + # observed peaks up to 2.7 MB/s during real manifest storms + # 512 kB/s sits ~280x above healthy p99 and ~5x below the peaks. An + # earlier 50 kB/s threshold produced ~41 sustained 5-min samples across + # six healthy nodes over six days (i.e. routine paging); 512 kB/s reduces + # that to 2 while still catching every genuine storm. + # + # The uptime gate exists because the startup manifest burst is MEASURED + # NORMAL behaviour. It does not hide real floods — the same 7-day sample + # shows firing rates with and without the gate within a factor of two — but a + # flood confined to the first 30 minutes after boot is deliberately not + # alerted. ManifestJobQueueConvoy covers that window via the job pool. - uid: xrpld-manifest-flood-inbound title: ManifestFloodInbound condition: C @@ -850,7 +869,7 @@ groups: summary: "Inbound manifest flood on {{ $labels.service_instance_id }}" description: >- Node {{ $labels.service_instance_id }} is receiving - {{ $values.B.Value }} B/s of manifest traffic (>50 kB/s) over 10m. + {{ $values.B.Value }} B/s of manifest traffic (>512 kB/s) over 10m. A peer is flooding oversized TMManifests dumps. data: - refId: A @@ -893,7 +912,7 @@ groups: conditions: - evaluator: type: gt - params: [51200] + params: [524288] datasource: type: __expr__ uid: __expr__ From feabcc5b895534e6348351fa6fe3fa43e3dbacd7 Mon Sep 17 00:00:00 2001 From: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> Date: Tue, 4 Aug 2026 15:14:54 +0100 Subject: [PATCH 14/32] docs(telemetry): sync alerting runbook with corrected thresholds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update the catalogue table and prose for the two thresholds corrected by the 7-day backtest, and document why ValidatedLedgerStale must exclude the 1209600s sentinel that getValidatedLedgerAge() returns when no validated ledger exists — that clause looks redundant and would otherwise be removed as a simplification. Also record the tuning lesson: justify thresholds from a 7-day sample, not 24 hours. --- docs/telemetry-runbook.md | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/docs/telemetry-runbook.md b/docs/telemetry-runbook.md index 813c7bf9b7..8582a8dd6a 100644 --- a/docs/telemetry-runbook.md +++ b/docs/telemetry-runbook.md @@ -1224,7 +1224,7 @@ Alerts fire only after the condition holds for the `for` dwell time. | `NodeStateFlapping` | warning | > 3 re-entries into FULL per hour | 15m | | `NodeNotFull` | warning | `server_state` < 4 (FULL) | 15m | | `ManifestJobQueueConvoy` | warning | `jobq_manifest_waiting` > 3 | 10m | -| `ManifestFloodInbound` | warning | `rate(overhead_manifest_bytes_in)` > 50 kB/s | 10m | +| `ManifestFloodInbound` | warning | `rate(overhead_manifest_bytes_in)` > 512 kB/s | 10m | | `PeerResourceDisconnects` | warning | > 5 resource-driven peer disconnects per 30m | 5m | Two expression idioms recur and are load-bearing — do not "simplify" them away: @@ -1253,8 +1253,17 @@ node is likely down. Check peer count and process health first. **ValidatedLedgerStale** — The validated ledger has fallen more than 60s behind. This is the clearest single "is this node healthy" signal on XRPL: it is the symptom nearly every consensus or sync failure eventually produces, so it is -often the first thing to check and the last thing to clear. Measured p95 is ~4s -on a healthy node. +often the first thing to check and the last thing to clear. Measured over 7 days: +p50 2s, p95 4s, p99 5s on every node. + +> **The `< 1209600` clause in this rule's expression is required — do not remove +> it.** When a node holds no validated ledger at all, +> `LedgerMaster::getValidatedLedgerAge()` returns `weeks{2}` (1 209 600 s) as a +> **sentinel**, not a measurement. Without the clause the rule reads that as "14 +> days stale" and fires on every node during startup — measured, it produced +> sustained firing on all nine nodes over a six-day window, healthy ones included. +> A node genuinely stuck without a validated ledger is caught by +> `LedgerCloseStalled` and `NodeNotFull` instead. #### Validator health @@ -1335,11 +1344,16 @@ This is the most reliable manifest-flood signal because `jobq_manifest_waiting` is `0` at the 99.9th percentile on every node over 24h — any sustained backlog is a genuine outlier rather than normal variance. -**ManifestFloodInbound** — Inbound manifest byte-rate exceeds 50 kB/s. Catches the +**ManifestFloodInbound** — Inbound manifest byte-rate exceeds 512 kB/s. Catches the wire-level cause (a peer shipping oversized dumps) even when the job pool absorbs -it without a visible backlog. Measured steady state is 0.7-2.3 kB/s against a p99 -of 267-420 kB/s during the startup flood, so the threshold sits ~20x above normal -and well below a real flood. +it without a visible backlog. Measured over 7 days: healthy p95 0.2-0.5 kB/s and +p99 1.0-1.8 kB/s, against peaks up to 2.7 MB/s during real storms — so the +threshold sits ~280x above healthy p99 and ~5x below the peaks. + +> An earlier revision used 50 kB/s, justified from a 24-hour window. Over a full +> week that produced ~41 sustained 5-minute firings across six **healthy** nodes, +> i.e. routine paging. Prefer a 7-day sample when tuning any threshold here; 24 +> hours is too short to expose weekly variation. > **Both manifest rules deliberately suppress startup.** The manifest storm at > boot is _measured normal behaviour_, so `ManifestFloodInbound` carries an From 3860c93db2968a809a852fc8c1475ae6f8a49ebb Mon Sep 17 00:00:00 2001 From: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> Date: Tue, 4 Aug 2026 16:10:04 +0100 Subject: [PATCH 15/32] refactor(telemetry): route dashboards, runbook and collector work to phase-9 These changes were developed on the phase-10 branch but belong to content this branch and its upstreams introduced. Carrying them on phase-10 made its PR diff report churn in files phase-10 does not own, and left each PR claiming a scope that did not match its contents. Moved here from phase-10 (identical content, no functional change): - Dashboards: all 14 existing boards plus the new log-derived-insights board. - Docs: telemetry-runbook.md (minus the workload/benchmark sections, which describe phase-10 tooling) and the new telemetry-glossary.md. - Grafana Cloud + Alloy export path: collector config, compose override, the two .env examples and alloy/config.alloy. - Local stack: otel-collector-config.yaml gains sub-millisecond and second-scale spanmetrics buckets, pins unit=ms, and promotes close_time_correct; integration-test.sh and TESTING.md follow. - Node configs: exported_instance -> service_instance_id in comments; the mainnet sample now logs at warning to bound log volume. - Metrics code: Telemetry.cpp builds the metrics pipeline in the constructor via initMetrics() so the global MeterProvider is published before any subsystem creates a beast::insight instrument, and the histogram view keeps each instrument's own name instead of collapsing them under one series. MetricsRegistry gains a last_close_time gauge and skips negative job-queue durations. OTelCollector drops an unused accessor. - Naming CI: xrpl_work_item joins EXTERNAL_INFRA_LABELS and Rule E accepts the dotted perf-iac resource-attribute form. This must travel with the dashboards and runbook that reference those labels, or the rules fail. - Doxygen input glob no longer recurses dot-directories. Sections describing phase-10 tooling stay on phase-10 and keep their "Future Enhancement" / "Planned, not yet implemented" markers here; phase-10 removes those markers when it lands the tooling. --- .cspell.config.yaml | 4 + .../scripts/otel-naming/check_otel_naming.py | 21 +- .../otel-naming/test_check_otel_naming.py | 8 + .gitignore | 1 + .../05-configuration-reference.md | 2 +- OpenTelemetryPlan/06-implementation-phases.md | 294 +- OpenTelemetryPlan/08-appendix.md | 88 +- .../09-data-collection-reference.md | 50 +- OpenTelemetryPlan/OpenTelemetryPlan.md | 2 +- OpenTelemetryPlan/Phase11_taskList.md | 91 + OpenTelemetryPlan/Phase3_taskList.md | 2 +- OpenTelemetryPlan/Phase4_taskList.md | 4 +- OpenTelemetryPlan/Phase9_taskList.md | 36 +- cmake/XrplDocs.cmake | 6 +- .../telemetry/.env.grafanacloud-alloy.example | 27 + docker/telemetry/.env.grafanacloud.example | 16 + docker/telemetry/.gitignore | 5 + docker/telemetry/TESTING.md | 81 +- docker/telemetry/alloy/config.alloy | 279 + .../docker-compose.grafanacloud.yaml | 28 + .../grafana/dashboards/consensus-health.json | 272 +- .../grafana/dashboards/fee-market.json | 170 +- .../grafana/dashboards/job-queue.json | 50 +- .../grafana/dashboards/ledger-data-sync.json | 77 +- .../grafana/dashboards/ledger-operations.json | 215 +- .../dashboards/log-derived-insights.json | 2471 ++++++ .../grafana/dashboards/network-traffic.json | 738 +- .../grafana/dashboards/node-health.json | 7300 +++++++++++------ .../dashboards/overlay-traffic-detail.json | 319 +- .../grafana/dashboards/peer-network.json | 125 +- .../grafana/dashboards/peer-quality.json | 146 +- .../grafana/dashboards/rpc-pathfinding.json | 191 +- .../grafana/dashboards/rpc-performance.json | 449 +- .../dashboards/transaction-overview.json | 231 +- .../grafana/dashboards/validator-health.json | 296 +- docker/telemetry/integration-test.sh | 79 +- .../otel-collector-config.grafanacloud.yaml | 280 + docker/telemetry/otel-collector-config.yaml | 45 +- docker/telemetry/xrpld-telemetry-mainnet.cfg | 4 +- docker/telemetry/xrpld-telemetry.cfg | 2 +- docs/telemetry-glossary.md | 915 +++ docs/telemetry-runbook.md | 1180 ++- src/libxrpl/beast/insight/OTelCollector.cpp | 23 +- src/libxrpl/telemetry/Telemetry.cpp | 77 +- src/xrpld/app/main/Application.cpp | 2 +- src/xrpld/app/main/CollectorManager.cpp | 2 +- src/xrpld/telemetry/MetricsRegistry.cpp | 31 +- src/xrpld/telemetry/MetricsRegistry.h | 2 +- 48 files changed, 12682 insertions(+), 4055 deletions(-) create mode 100644 docker/telemetry/.env.grafanacloud-alloy.example create mode 100644 docker/telemetry/.env.grafanacloud.example create mode 100644 docker/telemetry/alloy/config.alloy create mode 100644 docker/telemetry/docker-compose.grafanacloud.yaml create mode 100644 docker/telemetry/grafana/dashboards/log-derived-insights.json create mode 100644 docker/telemetry/otel-collector-config.grafanacloud.yaml create mode 100644 docs/telemetry-glossary.md diff --git a/.cspell.config.yaml b/.cspell.config.yaml index 61e45373bc..99a97f09cc 100644 --- a/.cspell.config.yaml +++ b/.cspell.config.yaml @@ -112,6 +112,7 @@ words: - disablerepo - distro - doxyfile + - dthms - dxrpl - elgamal - enabled @@ -231,6 +232,7 @@ words: - onlatest - ostr - otelc + - otelcol - oxalica - pargs - partitioner @@ -261,6 +263,7 @@ words: - qalloc - qbsprofile - queuable + - ransitioned - Raphson - rcflags - reparent @@ -362,6 +365,7 @@ words: - unfindable - unflatten - unfund + - unheld - unimpair - unroutable - unscalable diff --git a/.github/scripts/otel-naming/check_otel_naming.py b/.github/scripts/otel-naming/check_otel_naming.py index 6bf6a6a266..49a4e606a7 100644 --- a/.github/scripts/otel-naming/check_otel_naming.py +++ b/.github/scripts/otel-naming/check_otel_naming.py @@ -19,12 +19,16 @@ Design principles and the `join(seg::..., ...)` dotted resource compositions), and * the keys the code passes to `Resource::Create({ ... })` in Telemetry.cpp (the standard `semconv::service::*` keys -> service.name/version/...). - The one narrow, explicit exception is EXTERNAL_INFRA_LABELS (Rule D): + The one narrow, explicit exception is EXTERNAL_INFRA_LABELS (Rules D & E): identity labels stamped by infrastructure outside this repo's OTel code (the perf-iac harness), which by definition have no source in-tree to derive from. Kept separate from the generic Prometheus/Grafana builtins set so the exception stays visible rather than blending into "things - every OTel setup has". + every OTel setup has". perf-iac's alloy pipeline stamps each identity at + two layers -- dotted on the OTel resource attribute (xrpl.work.item/ + .branch/.node.role, checked by Rule E) and underscore on the derived + Prometheus metric-datapoint label (xrpl_work_item/_branch/_node_role, + checked by Rule D) -- so both forms are exempt from the same constant. 2. Presence-gated enforcement. Every rule runs ONLY when the source files it needs are present in the tree, and is otherwise skipped (never failed). This @@ -63,7 +67,9 @@ Rules (each FAILS the build, when its inputs are present) native-metric label, or a builtin. TraceQL `span.`/`resource.` scope prefixes are stripped before the L1 lookup. E No dotted `xrpl..` attribute key in the runbook (only the - L1 resource attrs xrpl.network.* may be dotted). Span names, filenames, + L1 resource attrs xrpl.network.* and the EXTERNAL_INFRA_LABELS dotted + form -- xrpl.work.item/.branch/.node.role -- may be dotted). Span names, + filenames, OTel-standard keys, and metric labels are not flagged. Warnings (printed, but do NOT fail the build) @@ -851,6 +857,7 @@ def metric_label_names(root: Path) -> Set[str]: # repo's OTel code (never as a workaround for a dashboard querying a label # that nothing actually emits — that is a real Rule D violation). EXTERNAL_INFRA_LABELS = { + "xrpl_work_item", # perf-iac: ticket/work-item id for the perf comparison run "xrpl_branch", # perf-iac: git ref of the xrpld build under test "xrpl_node_role", # perf-iac: validator/peer role in the perf cluster } @@ -934,10 +941,18 @@ def run_rule_e_runbook(root: Path, l1_keys: Set[str], report: Report) -> None: # Legitimate dotted resource attrs (`xrpl.network.id`/`.type`) are in L1 and # are skipped. A dotted `xrpl.` token absent from L1 is a genuine doc/code # mismatch (e.g. `xrpl.tx.hash` where the code emits `tx_hash`). + # EXTERNAL_INFRA_LABELS (Rule D) holds the underscore/metric-label form of + # the perf-iac identity attrs; the resource-attribute layer stamps the same + # identities dotted (xrpl.work.item/.branch/.node.role -- see the alloy + # pipeline that owns them), so also skip a token whose dotted-to-underscore + # form is in that set. + external_infra_dotted = {lbl.replace("_", ".") for lbl in EXTERNAL_INFRA_LABELS} for m in re.finditer(r"`(xrpl\.[a-z][a-z0-9_.]*)`", text): token = m.group(1) if token in l1_keys: # legitimate dotted resource attr (xrpl.network.*) continue + if token in external_infra_dotted: # perf-iac resource-attribute layer + continue found = True report.violation( "E", str(path.relative_to(root)), token, "underscore, not dotted" diff --git a/.github/scripts/otel-naming/test_check_otel_naming.py b/.github/scripts/otel-naming/test_check_otel_naming.py index 99beec8928..05500768eb 100644 --- a/.github/scripts/otel-naming/test_check_otel_naming.py +++ b/.github/scripts/otel-naming/test_check_otel_naming.py @@ -121,6 +121,14 @@ class RuleERunbook(unittest.TestCase): def test_legit_dotted_resource_attrs_in_l1(self): self.assertEqual(_run_rule_e("`xrpl.network.id` `xrpl.network.type`"), []) + def test_external_infra_dotted_resource_attrs_not_flagged(self): + # perf-iac stamps these as dotted resource attrs (alloy pipeline); + # EXTERNAL_INFRA_LABELS (Rule D) holds their underscore metric-label + # form -- Rule E must also exempt the dotted resource-attr form. + self.assertEqual( + _run_rule_e("`xrpl.work.item` `xrpl.branch` `xrpl.node.role`"), [] + ) + def test_prose_word(self): self.assertEqual(_run_rule_e("the `command` attribute"), []) diff --git a/.gitignore b/.gitignore index 13b59a7e2c..92b4e56d07 100644 --- a/.gitignore +++ b/.gitignore @@ -89,3 +89,4 @@ target/ # clangd cache /.cache +.claude/ diff --git a/OpenTelemetryPlan/05-configuration-reference.md b/OpenTelemetryPlan/05-configuration-reference.md index a09fcb48e3..cc3a83b347 100644 --- a/OpenTelemetryPlan/05-configuration-reference.md +++ b/OpenTelemetryPlan/05-configuration-reference.md @@ -266,7 +266,7 @@ In the Prometheus datasource, set `exemplarTraceIdDestinations` to map the `trac **Step 4: Dashboard panel with exemplars** -Add a timeseries panel over Prometheus (e.g. `histogram_quantile(0.99, rate(xrpld_rpc_duration_seconds_bucket[5m]))`) with `exemplar: true` enabled. +Add a timeseries panel over Prometheus (e.g. `histogram_quantile(0.99, rate(rpc_duration_seconds_bucket[5m]))`) with `exemplar: true` enabled. This allows clicking on metric data points to jump directly to the related trace. diff --git a/OpenTelemetryPlan/06-implementation-phases.md b/OpenTelemetryPlan/06-implementation-phases.md index ef311643f7..500fc760d1 100644 --- a/OpenTelemetryPlan/06-implementation-phases.md +++ b/OpenTelemetryPlan/06-implementation-phases.md @@ -336,21 +336,21 @@ xrpld has a mature metrics framework (`beast::insight`) that emits StatsD-format ### Metric Inventory -| Category | Group | Type | Count | Key Metrics | -| --------------- | ------------------ | ------------- | ---------- | ------------------------------------------------------ | -| Node State | `State_Accounting` | Gauge | 10 | `*_duration`, `*_transitions` per operating mode | -| Ledger | `LedgerMaster` | Gauge | 2 | `Validated_Ledger_Age`, `Published_Ledger_Age` | -| Ledger Fetch | — | Counter | 1 | `ledger_fetches` | -| Ledger History | `ledger.history` | Counter | 1 | `mismatch` | -| RPC | `rpc` | Counter+Event | 3 | `requests`, `time` (histogram), `size` (histogram) | -| Job Queue | — | Gauge+Event | 1 + 2×N | `job_count`, per-job `{name}` and `{name}_q` | -| Peer Finder | `Peer_Finder` | Gauge | 2 | `Active_Inbound_Peers`, `Active_Outbound_Peers` | -| Overlay | `Overlay` | Gauge | 1 | `Peer_Disconnects` | -| Overlay Traffic | per-category | Gauge | 4×57 = 228 | `Bytes_In/Out`, `Messages_In/Out` per traffic category | -| Pathfinding | — | Event | 2 | `pathfind_fast`, `pathfind_full` (histograms) | -| I/O | — | Event | 1 | `ios_latency` (histogram) | -| Resource Mgr | — | Meter | 2 | `warn`, `drop` (rate counters) | -| Caches | per-cache | Gauge | 2×N | `{cache}.size`, `{cache}.hit_rate` | +| Category | Group | Type | Count | Key Metrics | +| --------------- | ------------------ | ------------- | ---------- | ----------------------------------------------------------------------------------------------------------- | +| Node State | `State_Accounting` | Gauge | 10 | `*_duration`, `*_transitions` per operating mode | +| Ledger | `LedgerMaster` | Gauge | 2 | `Validated_Ledger_Age`, `Published_Ledger_Age` | +| Ledger Fetch | — | Counter | 1 | `ledger_fetches` | +| Ledger History | `ledger.history` | Counter | 1 | `mismatch` | +| RPC | `rpc` | Counter+Event | 3 | `requests`, `time` (histogram), `size` (histogram) | +| Job Queue | `jobq` | Gauge+Event | 1 + 2×N | `job_count`, per-job `{name}` and `{name}_q` (emitted with the `jobq_` group prefix, e.g. `jobq_job_count`) | +| Peer Finder | `Peer_Finder` | Gauge | 2 | `Active_Inbound_Peers`, `Active_Outbound_Peers` | +| Overlay | `Overlay` | Gauge | 1 | `Peer_Disconnects` | +| Overlay Traffic | per-category | Gauge | 4×57 = 228 | `Bytes_In/Out`, `Messages_In/Out` per traffic category | +| Pathfinding | — | Event | 2 | `pathfind_fast`, `pathfind_full` (histograms) | +| I/O | — | Event | 1 | `ios_latency` (histogram) | +| Resource Mgr | — | Meter | 2 | `warn`, `drop` (rate counters) | +| Caches | per-cache | Gauge | 2×N | `{cache}.size`, `{cache}.hit_rate` | **Total**: ~255+ unique metrics (plus dynamic job-type and cache metrics) @@ -390,10 +390,10 @@ The `StatsDMeterImpl` in `StatsDCollector.cpp` sends metrics with `|m` suffix, w ### Exit Criteria -- [ ] StatsD metrics visible in Prometheus (`curl localhost:9090/api/v1/query?query=xrpld_LedgerMaster_Validated_Ledger_Age`) +- [ ] StatsD metrics visible in Prometheus (`curl localhost:9090/api/v1/query?query=ledgermaster_validated_ledger_age`) - [ ] All 3 new Grafana dashboards load without errors - [ ] Integration test verifies at least core StatsD metrics (ledger age, peer counts, RPC requests) -- [ ] ~~Meter metrics (`warn`, `drop`) flow correctly after `|m` → `|c` fix~~ — DEFERRED (breaking change, tracked separately) +- [ ] ~~Meter metrics (`warn`, `drop`) flow correctly after `|m` → `|c` fix~~ — DEFERRED (breaking change, tracked separately; resolved by Phase 7's OTel Counter mapping) --- @@ -500,7 +500,7 @@ graph LR BP -->|"OTLP/gRPC"| D SM -->|"RED metrics"| E - R1 -->|"xrpld_* metrics
(native OTLP)"| E + R1 -->|"system metrics
(native OTLP)"| E E --> F D --> F @@ -581,11 +581,11 @@ See [Phase7_taskList.md](./Phase7_taskList.md) for detailed per-task breakdown. ### Motivation -xrpld's `beast::Journal` logs and OpenTelemetry traces are currently two disjoint observability signals. When investigating an issue, operators must manually correlate timestamps between log files and Jaeger/Tempo traces. Phase 8 bridges this gap by injecting trace context (`trace_id`, `span_id`) into every log line emitted within an active span, and ingesting those logs into Grafana Loki via the OTel Collector's filelog receiver. +xrpld's `beast::Journal` logs and OpenTelemetry traces are currently two disjoint observability signals. When investigating an issue, operators must manually correlate timestamps between log files and Tempo traces. Phase 8 bridges this gap by injecting trace context (`trace_id`, `span_id`) into every log line emitted within an active span, and ingesting those logs into Grafana Loki via the OTel Collector's filelog receiver. #### Gains -1. **One-click trace-to-log navigation** — Click a trace in Tempo/Jaeger and immediately see the corresponding log lines in Loki, filtered by `trace_id`. +1. **One-click trace-to-log navigation** — Click a trace in Tempo and immediately see the corresponding log lines in Loki, filtered by `trace_id`. 2. **Reverse lookup (log-to-trace)** — Loki derived fields make `trace_id` values clickable links back to Tempo. 3. **Unified observability** — All three pillars (traces, metrics, logs) flow through the same OTel Collector pipeline and are visible in a single Grafana instance. 4. **Zero new dependencies in xrpld** — Uses existing OTel SDK headers (`GetSpan`, `GetContext`) already linked in Phase 1. @@ -1215,19 +1215,19 @@ Clear, measurable criteria for each phase. ### 6.12.6 Success Metrics Summary -| Phase | Primary Metric | Secondary Metric | Deadline | Status | -| -------- | -------------------------------- | --------------------------- | -------------- | ------------------ | -| Phase 1 | SDK compiles and runs | Zero overhead when disabled | End of Week 2 | Active | -| Phase 2 | 100% RPC coverage | <1ms latency overhead | End of Week 4 | Active | -| Phase 3 | Cross-node traces work | <5% throughput impact | End of Week 6 | Active | -| Phase 4 | Consensus fully traced | No consensus timing impact | End of Week 8 | Active | -| Phase 5 | Production deployment | Operators trained | End of Week 9 | Active | -| Phase 6 | StatsD metrics in Prometheus | 3 dashboards operational | End of Week 10 | Active | -| Phase 7 | All metrics via OTLP | No StatsD dependency | End of Week 12 | Active | -| Phase 8 | trace_id in logs + Loki | Tempo↔Loki correlation | End of Week 13 | Active | -| Phase 9 | 68+ new internal metrics in Prom | 2 new dashboards | End of Week 15 | Future Enhancement | -| Phase 10 | Full telemetry stack validated | < 3% CPU overhead proven | End of Week 17 | Future Enhancement | -| Phase 11 | Third-party metrics via receiver | 4 new dashboards + alerting | End of Week 20 | Future Enhancement | +| Phase | Primary Metric | Secondary Metric | Deadline | Status | +| -------- | ------------------------------------------------------------------ | --------------------------- | -------------- | ------------------ | +| Phase 1 | SDK compiles and runs | Zero overhead when disabled | End of Week 2 | Active | +| Phase 2 | 100% RPC coverage | <1ms latency overhead | End of Week 4 | Active | +| Phase 3 | Cross-node traces work | <5% throughput impact | End of Week 6 | Active | +| Phase 4 | Consensus fully traced | No consensus timing impact | End of Week 8 | Active | +| Phase 5 | Production deployment | Operators trained | End of Week 9 | Active | +| Phase 6 | StatsD metrics in Prometheus | 3 dashboards operational | End of Week 10 | Active | +| Phase 7 | All metrics via OTLP | No StatsD dependency | End of Week 12 | Active | +| Phase 8 | trace_id in logs + Loki | Tempo↔Loki correlation | End of Week 13 | Active | +| Phase 9 | 68+ new internal metrics in Prom | 2 new dashboards | End of Week 15 | Future Enhancement | +| Phase 10 | Full telemetry stack validated; OTel-sourced regression gate in CI | < 3% CPU overhead proven | End of Week 17 | Future Enhancement | +| Phase 11 | Third-party metrics via receiver | 4 new dashboards + alerting | End of Week 20 | Future Enhancement | --- @@ -1469,12 +1469,12 @@ The overlay already tracks resource-limit disconnects via `OverlayImpl::Stats::p **What to do**: -- Ensure `xrpld_Overlay_Peer_Disconnects_Charges` appears in the StatsD-to-Prometheus metric name mapping +- Ensure `overlay_peer_disconnects_charges` appears in the StatsD-to-Prometheus metric name mapping - Verify the metric appears in Prometheus after StatsD bridge is active **File**: `src/xrpld/overlay/detail/OverlayImpl.cpp` -**Prometheus name**: `xrpld_Overlay_Peer_Disconnects_Charges` +**Prometheus name**: `overlay_peer_disconnects_charges` --- @@ -1581,12 +1581,12 @@ class ValidationTracker New MetricsRegistry observable gauge for amendment, UNL, and quorum health. -| Gauge Name | Label `metric=` | Type | Source | -| ------------------------ | ------------------- | ------ | ------------------------------------------------- | -| `xrpld_validator_health` | `amendment_blocked` | int64 | `app_.getOPs().isAmendmentBlocked()` → 0/1 | -| | `unl_blocked` | int64 | `app_.getOPs().isUNLBlocked()` → 0/1 | -| | `unl_expiry_days` | double | `app_.validators().expires()` → days until expiry | -| | `validation_quorum` | int64 | `app_.validators().quorum()` | +| Gauge Name | Label `metric=` | Type | Source | +| ------------------ | ------------------- | ------ | ------------------------------------------------- | +| `validator_health` | `amendment_blocked` | int64 | `app_.getOPs().isAmendmentBlocked()` → 0/1 | +| | `unl_blocked` | int64 | `app_.getOPs().isUNLBlocked()` → 0/1 | +| | `unl_expiry_days` | double | `app_.validators().expires()` → days until expiry | +| | `validation_quorum` | int64 | `app_.validators().quorum()` | **File**: `src/xrpld/telemetry/MetricsRegistry.cpp` (new gauge callback in `registerAsyncGauges()`) @@ -1602,12 +1602,12 @@ New MetricsRegistry observable gauge for amendment, UNL, and quorum health. New MetricsRegistry observable gauge for peer health aggregates. -| Gauge Name | Label `metric=` | Type | Source | -| -------------------- | -------------------------- | ------ | ------------------------------------------ | -| `xrpld_peer_quality` | `peer_latency_p90_ms` | double | Iterate peers, compute P90 from `latency_` | -| | `peers_insane_count` | int64 | Count peers with `tracking_ == diverged` | -| | `peers_higher_version_pct` | double | Compare `getVersion()` to own version | -| | `upgrade_recommended` | int64 | 1 if `peers_higher_version_pct > 60%` | +| Gauge Name | Label `metric=` | Type | Source | +| -------------- | -------------------------- | ------ | ------------------------------------------ | +| `peer_quality` | `peer_latency_p90_ms` | double | Iterate peers, compute P90 from `latency_` | +| | `peers_insane_count` | int64 | Count peers with `tracking_ == diverged` | +| | `peers_higher_version_pct` | double | Compare `getVersion()` to own version | +| | `upgrade_recommended` | int64 | 1 if `peers_higher_version_pct > 60%` | **Implementation note**: The callback iterates `app_.overlay().foreach(...)` to collect per-peer latency and version data. This runs every 10s on the metrics reader thread — acceptable overhead for ~50-200 peers. @@ -1626,13 +1626,13 @@ New MetricsRegistry observable gauge for peer health aggregates. New MetricsRegistry observable gauge for fee and ledger metrics. -| Gauge Name | Label `metric=` | Type | Source | -| ---------------------- | -------------------- | ------ | ----------------------------------------- | -| `xrpld_ledger_economy` | `base_fee_xrp` | double | `app_.getFeeTrack().getBaseFee()` → drops | -| | `reserve_base_xrp` | double | From validated ledger fee settings | -| | `reserve_inc_xrp` | double | From validated ledger fee settings | -| | `ledger_age_seconds` | double | `now - lastValidatedCloseTime` | -| | `transaction_rate` | double | Derived: tx count delta / time delta | +| Gauge Name | Label `metric=` | Type | Source | +| ---------------- | -------------------- | ------ | ----------------------------------------- | +| `ledger_economy` | `base_fee_xrp` | double | `app_.getFeeTrack().getBaseFee()` → drops | +| | `reserve_base_xrp` | double | From validated ledger fee settings | +| | `reserve_inc_xrp` | double | From validated ledger fee settings | +| | `ledger_age_seconds` | double | `now - lastValidatedCloseTime` | +| | `transaction_rate` | double | Derived: tx count delta / time delta | **File**: `src/xrpld/telemetry/MetricsRegistry.cpp` @@ -1648,10 +1648,10 @@ New MetricsRegistry observable gauge for fee and ledger metrics. New MetricsRegistry observable gauge for node state duration. -| Gauge Name | Label `metric=` | Type | Source | -| ---------------------- | ------------------------------- | ------ | ------------------------------------------------ | -| `xrpld_state_tracking` | `state_value` | int64 | 0-7 numeric encoding matching external dashboard | -| | `time_in_current_state_seconds` | double | `now - lastModeChangeTime` | +| Gauge Name | Label `metric=` | Type | Source | +| ---------------- | ------------------------------- | ------ | ------------------------------------------------ | +| `state_tracking` | `state_value` | int64 | 0-7 numeric encoding matching external dashboard | +| | `time_in_current_state_seconds` | double | `now - lastModeChangeTime` | **State value encoding**: @@ -1680,9 +1680,9 @@ xrpld's `OperatingMode` enum maps 0-4 (DISCONNECTED through FULL). The external **Task 7.13: Storage Detail Observable Gauge** -| Gauge Name | Label `metric=` | Type | Source | -| ---------------------- | --------------------- | ----- | ---------------------------------------------------- | -| `xrpld_storage_detail` | `stored_object_bytes` | int64 | `Database::getStoreSize()` — cumulative object bytes | +| Gauge Name | Label `metric=` | Type | Source | +| ---------------- | --------------------- | ----- | ---------------------------------------------------- | +| `storage_detail` | `stored_object_bytes` | int64 | `Database::getStoreSize()` — cumulative object bytes | This is not a filesystem measurement. `getStoreSize()` sums the object payloads this process has written, so it excludes NuDB's keys, bucket padding and log, and it @@ -1709,15 +1709,15 @@ misdescribed it and the old name implied an on-disk size it never reported. New counters incremented at event sites. Declared in MetricsRegistry, recording sites added in consensus/overlay/network code. -| Counter Name | Increment Site | Source File | -| ----------------------------------- | -------------------------------- | --------------------- | -| `xrpld_ledgers_closed_total` | `onAccept()` in consensus | RCLConsensus.cpp | -| `xrpld_validations_sent_total` | `validate()` in consensus | RCLConsensus.cpp | -| `xrpld_validations_checked_total` | Network validation received | LedgerMaster.cpp | -| `xrpld_validation_agreements_total` | ValidationTracker reconciliation | ValidationTracker.cpp | -| `xrpld_validation_missed_total` | ValidationTracker reconciliation | ValidationTracker.cpp | -| `xrpld_state_changes_total` | `setMode()` in NetworkOPs | NetworkOPs.cpp | -| `xrpld_jq_trans_overflow_total` | Job queue overflow path | JobQueue.cpp | +| Counter Name | Increment Site | Source File | +| ----------------------------- | -------------------------------- | --------------------- | +| `ledgers_closed_total` | `onAccept()` in consensus | RCLConsensus.cpp | +| `validations_sent_total` | `validate()` in consensus | RCLConsensus.cpp | +| `validations_checked_total` | Network validation received | LedgerMaster.cpp | +| `validation_agreements_total` | ValidationTracker reconciliation | ValidationTracker.cpp | +| `validation_missed_total` | ValidationTracker reconciliation | ValidationTracker.cpp | +| `state_changes_total` | `setMode()` in NetworkOPs | NetworkOPs.cpp | +| `jq_trans_overflow_total` | Job queue overflow path | JobQueue.cpp | **Key modified files**: @@ -1738,14 +1738,14 @@ New counters incremented at event sites. Declared in MetricsRegistry, recording Reads from the `ValidationTracker` (Task 7.8) to export rolling window stats. -| Gauge Name | Label `metric=` | Type | Source | -| ---------------------------- | ------------------- | ------ | --------------------------- | -| `xrpld_validation_agreement` | `agreement_pct_1h` | double | `tracker.agreementPct1h()` | -| | `agreements_1h` | int64 | `tracker.agreements1h()` | -| | `missed_1h` | int64 | `tracker.missed1h()` | -| | `agreement_pct_24h` | double | `tracker.agreementPct24h()` | -| | `agreements_24h` | int64 | `tracker.agreements24h()` | -| | `missed_24h` | int64 | `tracker.missed24h()` | +| Gauge Name | Label `metric=` | Type | Source | +| ---------------------- | ------------------- | ------ | --------------------------- | +| `validation_agreement` | `agreement_pct_1h` | double | `tracker.agreementPct1h()` | +| | `agreements_1h` | int64 | `tracker.agreements1h()` | +| | `missed_1h` | int64 | `tracker.missed1h()` | +| | `agreement_pct_24h` | double | `tracker.agreementPct24h()` | +| | `agreements_24h` | int64 | `tracker.agreements24h()` | +| | `missed_24h` | int64 | `tracker.missed24h()` | **File**: `src/xrpld/telemetry/MetricsRegistry.cpp` @@ -1765,21 +1765,21 @@ Reads from the `ValidationTracker` (Task 7.8) to export rolling window stats. New Grafana dashboard: `validator-health.json` -| Panel | Type | PromQL | -| -------------------------- | ---------- | -------------------------------------------------------------- | -| Agreement % (1h) | stat | `xrpld_validation_agreement{metric="agreement_pct_1h"}` | -| Agreement % (24h) | stat | `xrpld_validation_agreement{metric="agreement_pct_24h"}` | -| Agreements vs Missed (1h) | bargauge | `agreements_1h` and `missed_1h` side by side | -| Agreements vs Missed (24h) | bargauge | `agreements_24h` and `missed_24h` side by side | -| Validation Rate | stat | `rate(xrpld_validations_sent_total[5m]) * 60` | -| Validations Checked Rate | stat | `rate(xrpld_validations_checked_total[5m]) * 60` | -| Amendment Blocked | stat | `xrpld_validator_health{metric="amendment_blocked"}` | -| UNL Expiry (days) | stat | `xrpld_validator_health{metric="unl_expiry_days"}` | -| Validation Quorum | stat | `xrpld_validator_health{metric="validation_quorum"}` | -| State Value Timeline | timeseries | `xrpld_state_tracking{metric="state_value"}` | -| Time in Current State | stat | `xrpld_state_tracking{metric="time_in_current_state_seconds"}` | -| State Changes Rate | stat | `rate(xrpld_state_changes_total[1h])` | -| Ledgers Closed Rate | stat | `rate(xrpld_ledgers_closed_total[5m]) * 60` | +| Panel | Type | PromQL | +| -------------------------- | ---------- | -------------------------------------------------------- | +| Agreement % (1h) | stat | `validation_agreement{metric="agreement_pct_1h"}` | +| Agreement % (24h) | stat | `validation_agreement{metric="agreement_pct_24h"}` | +| Agreements vs Missed (1h) | bargauge | `agreements_1h` and `missed_1h` side by side | +| Agreements vs Missed (24h) | bargauge | `agreements_24h` and `missed_24h` side by side | +| Validation Rate | stat | `rate(validations_sent_total[5m]) * 60` | +| Validations Checked Rate | stat | `rate(validations_checked_total[5m]) * 60` | +| Amendment Blocked | stat | `validator_health{metric="amendment_blocked"}` | +| UNL Expiry (days) | stat | `validator_health{metric="unl_expiry_days"}` | +| Validation Quorum | stat | `validator_health{metric="validation_quorum"}` | +| State Value Timeline | timeseries | `state_tracking{metric="state_value"}` | +| Time in Current State | stat | `state_tracking{metric="time_in_current_state_seconds"}` | +| State Changes Rate | stat | `rate(state_changes_total[1h])` | +| Ledgers Closed Rate | stat | `rate(ledgers_closed_total[5m]) * 60` | **Dashboard conventions**: `$node` template variable for `service_instance_id` filtering, dark theme, matching existing panel sizes and color schemes. @@ -1789,14 +1789,14 @@ New Grafana dashboard: `validator-health.json` New Grafana dashboard: `peer-quality.json` -| Panel | Type | PromQL | -| ---------------------- | ---------- | -------------------------------------------------------------- | -| P90 Peer Latency | timeseries | `xrpld_peer_quality{metric="peer_latency_p90_ms"}` | -| Insane/Diverged Peers | stat | `xrpld_peer_quality{metric="peers_insane_count"}` | -| Higher Version Peers % | stat | `xrpld_peer_quality{metric="peers_higher_version_pct"}` | -| Upgrade Recommended | stat | `xrpld_peer_quality{metric="upgrade_recommended"}` | -| Resource Disconnects | timeseries | `xrpld_Overlay_Peer_Disconnects_Charges` | -| Inbound vs Outbound | bargauge | `xrpld_Peer_Finder_Active_Inbound_Peers`, `..._Outbound_Peers` | +| Panel | Type | PromQL | +| ---------------------- | ---------- | -------------------------------------------------------- | +| P90 Peer Latency | timeseries | `peer_quality{metric="peer_latency_p90_ms"}` | +| Insane/Diverged Peers | stat | `peer_quality{metric="peers_insane_count"}` | +| Higher Version Peers % | stat | `peer_quality{metric="peers_higher_version_pct"}` | +| Upgrade Recommended | stat | `peer_quality{metric="upgrade_recommended"}` | +| Resource Disconnects | timeseries | `overlay_peer_disconnects_charges` | +| Inbound vs Outbound | bargauge | `peer_finder_active_inbound_peers`, `..._outbound_peers` | --- @@ -1804,13 +1804,13 @@ New Grafana dashboard: `peer-quality.json` Add a "Ledger Economy" row to the existing `node-health.json` dashboard: -| Panel | Type | PromQL | -| -------------------- | ---------- | --------------------------------------------------- | -| Base Fee (drops) | stat | `xrpld_ledger_economy{metric="base_fee_xrp"}` | -| Reserve Base (drops) | stat | `xrpld_ledger_economy{metric="reserve_base_xrp"}` | -| Reserve Inc (drops) | stat | `xrpld_ledger_economy{metric="reserve_inc_xrp"}` | -| Ledger Age | stat | `xrpld_ledger_economy{metric="ledger_age_seconds"}` | -| Transaction Rate | timeseries | `xrpld_ledger_economy{metric="transaction_rate"}` | +| Panel | Type | PromQL | +| -------------------- | ---------- | --------------------------------------------- | +| Base Fee (drops) | stat | `ledger_economy{metric="base_fee_xrp"}` | +| Reserve Base (drops) | stat | `ledger_economy{metric="reserve_base_xrp"}` | +| Reserve Inc (drops) | stat | `ledger_economy{metric="reserve_inc_xrp"}` | +| Ledger Age | stat | `ledger_economy{metric="ledger_age_seconds"}` | +| Transaction Rate | timeseries | `ledger_economy{metric="transaction_rate"}` | --- @@ -1837,21 +1837,21 @@ Add checks to `validate_telemetry.py` for all new span attributes and metrics. **New metric existence checks (~13)**: -| Metric Name | -| -------------------------------------------------------- | -| `xrpld_validation_agreement{metric="agreement_pct_1h"}` | -| `xrpld_validation_agreement{metric="agreement_pct_24h"}` | -| `xrpld_validator_health{metric="amendment_blocked"}` | -| `xrpld_validator_health{metric="unl_expiry_days"}` | -| `xrpld_peer_quality{metric="peer_latency_p90_ms"}` | -| `xrpld_peer_quality{metric="peers_insane_count"}` | -| `xrpld_ledger_economy{metric="base_fee_xrp"}` | -| `xrpld_ledger_economy{metric="transaction_rate"}` | -| `xrpld_state_tracking{metric="state_value"}` | -| `xrpld_ledgers_closed_total` | -| `xrpld_validations_sent_total` | -| `xrpld_state_changes_total` | -| `xrpld_storage_detail{metric="stored_object_bytes"}` | +| Metric Name | +| -------------------------------------------------- | +| `validation_agreement{metric="agreement_pct_1h"}` | +| `validation_agreement{metric="agreement_pct_24h"}` | +| `validator_health{metric="amendment_blocked"}` | +| `validator_health{metric="unl_expiry_days"}` | +| `peer_quality{metric="peer_latency_p90_ms"}` | +| `peer_quality{metric="peers_insane_count"}` | +| `ledger_economy{metric="base_fee_xrp"}` | +| `ledger_economy{metric="transaction_rate"}` | +| `state_tracking{metric="state_value"}` | +| `ledgers_closed_total` | +| `validations_sent_total` | +| `state_changes_total` | +| `storage_detail{metric="stored_object_bytes"}` | **New dashboard load checks (~3)**: @@ -1884,36 +1884,36 @@ Port 18 alert rules from the external `xrpl-validator-dashboard` to Grafana aler **Critical Group** (8 rules, eval interval 10s): -| Rule | Condition | For | -| ------------------- | ------------------------------------------------------------- | --- | -| Agreement Below 90% | `xrpld_validation_agreement{metric="agreement_pct_24h"} < 90` | 30s | -| Not Proposing | `xrpld_state_tracking{metric="state_value"} < 6` | 10s | -| Unhealthy State | `xrpld_state_tracking{metric="state_value"} < 4` | 10s | -| Amendment Blocked | `xrpld_validator_health{metric="amendment_blocked"} == 1` | 1m | -| UNL Expiring | `xrpld_validator_health{metric="unl_expiry_days"} < 14` | 1h | -| High IO Latency | `histogram_quantile(0.95, xrpld_ios_latency_bucket) > 50` | 1m | -| High Load Factor | `xrpld_load_factor_metrics{metric="load_factor"} > 1000` | 1m | -| Peer Count Critical | `xrpld_server_info{metric="peers"} < 5` | 1m | +| Rule | Condition | For | +| ------------------- | ------------------------------------------------------- | --- | +| Agreement Below 90% | `validation_agreement{metric="agreement_pct_24h"} < 90` | 30s | +| Not Proposing | `state_tracking{metric="state_value"} < 6` | 10s | +| Unhealthy State | `state_tracking{metric="state_value"} < 4` | 10s | +| Amendment Blocked | `validator_health{metric="amendment_blocked"} == 1` | 1m | +| UNL Expiring | `validator_health{metric="unl_expiry_days"} < 14` | 1h | +| High IO Latency | `histogram_quantile(0.95, ios_latency_bucket) > 50` | 1m | +| High Load Factor | `load_factor_metrics{metric="load_factor"} > 1000` | 1m | +| Peer Count Critical | `server_info{metric="peers"} < 5` | 1m | **Network Group** (3 rules, eval interval 10s): -| Rule | Condition | For | -| ------------------------- | ----------------------------------------------------------------- | --- | -| Peer Drop >10% | `delta(xrpld_server_info{metric="peers"}[30s]) / ... * 100 < -10` | 30s | -| Peer Drop >30% | Same formula, threshold -30 | 30s | -| P90 Latency + Disconnects | `peer_latency_p90_ms > 500 AND rate(disconnects) > 0` | 2m | +| Rule | Condition | For | +| ------------------------- | ----------------------------------------------------------- | --- | +| Peer Drop >10% | `delta(server_info{metric="peers"}[30s]) / ... * 100 < -10` | 30s | +| Peer Drop >30% | Same formula, threshold -30 | 30s | +| P90 Latency + Disconnects | `peer_latency_p90_ms > 500 AND rate(disconnects) > 0` | 2m | **Performance Group** (7 rules, eval interval 10s): -| Rule | Condition | For | -| ------------------- | ------------------------------------------------------------ | --- | -| CPU High | Per-core CPU > 80% | 2m | -| Memory Critical | Memory usage > 90% | 1m | -| Disk Warning | Disk usage > 85% | 2m | -| Job Queue Overflow | `rate(xrpld_jq_trans_overflow_total[5m]) > 0` | 1m | -| Upgrade Recommended | `xrpld_peer_quality{metric="peers_higher_version_pct"} > 60` | 1m | -| TX Rate Drop | Transaction rate dropped > 50% in 5m window | 5m | -| Stale Ledger | `xrpld_ledger_economy{metric="ledger_age_seconds"} > 30` | 1m | +| Rule | Condition | For | +| ------------------- | ------------------------------------------------------ | --- | +| CPU High | Per-core CPU > 80% | 2m | +| Memory Critical | Memory usage > 90% | 1m | +| Disk Warning | Disk usage > 85% | 2m | +| Job Queue Overflow | `rate(jq_trans_overflow_total[5m]) > 0` | 1m | +| Upgrade Recommended | `peer_quality{metric="peers_higher_version_pct"} > 60` | 1m | +| TX Rate Drop | Transaction rate dropped > 50% in 5m window | 5m | +| Stale Ledger | `ledger_economy{metric="ledger_age_seconds"} > 30` | 1m | **Notification channels**: Template configs for Email/SMTP, Discord, Slack, PagerDuty. diff --git a/OpenTelemetryPlan/08-appendix.md b/OpenTelemetryPlan/08-appendix.md index 53d7b01e42..c75af13d12 100644 --- a/OpenTelemetryPlan/08-appendix.md +++ b/OpenTelemetryPlan/08-appendix.md @@ -59,85 +59,19 @@ ## 8.2 Span Hierarchy Visualization -> **TxQ** = Transaction Queue +The authoritative span-flow diagrams — a master overview plus per-stage +flowcharts (ingress, the shared apply pipeline, the consensus round, ledger +finalize, and the pathfinding / ledger-acquire side flows) — live in the operator +runbook. They map every span onto the **real xrpld control flow and XRPL protocol +order** (verified against code and `docs/consensus.md`, with file:line evidence), +label every node and branch with the span that represents that state or +transition, and call out where the OpenTelemetry span parent links diverge from +that flow. -```mermaid -flowchart TB - subgraph trace["Trace: Transaction Lifecycle"] - rpc["rpc.request
(entry point)"] - validate["tx.validate"] - relay["tx.relay
(parent span)"] +> **See**: [docs/telemetry-runbook.md § Protocol Span Flow](../docs/telemetry-runbook.md#protocol-span-flow). - subgraph peers["Peer Spans"] - p1["peer.send
Peer A"] - p2["peer.send
Peer B"] - p3["peer.send
Peer C"] - end - - subgraph pathfinding["PathFinding Spans"] - pathfind["pathfind.request"] - pathcomp["pathfind.compute"] - end - - consensus["consensus.round"] - apply["tx.apply"] - - subgraph txqueue["TxQ Spans"] - txq["txq.enqueue"] - txqApply["txq.apply"] - end - - feeCalc["fee.escalate"] - end - - subgraph validators["Validator Spans"] - valFetch["validator.list.fetch"] - valManifest["validator.manifest"] - end - - rpc --> validate - rpc --> pathfind - pathfind --> pathcomp - validate --> relay - relay --> p1 - relay --> p2 - relay --> p3 - p1 -.->|"context propagation"| consensus - consensus --> apply - apply --> txq - txq --> txqApply - txq --> feeCalc - - style trace fill:#0f172a,stroke:#020617,color:#fff - style peers fill:#1e3a8a,stroke:#172554,color:#fff - style pathfinding fill:#134e4a,stroke:#0f766e,color:#fff - style txqueue fill:#064e3b,stroke:#047857,color:#fff - style validators fill:#4c1d95,stroke:#6d28d9,color:#fff - style rpc fill:#1d4ed8,stroke:#1e40af,color:#fff - style validate fill:#047857,stroke:#064e3b,color:#fff - style relay fill:#047857,stroke:#064e3b,color:#fff - style p1 fill:#0e7490,stroke:#155e75,color:#fff - style p2 fill:#0e7490,stroke:#155e75,color:#fff - style p3 fill:#0e7490,stroke:#155e75,color:#fff - style consensus fill:#fef3c7,stroke:#fde68a,color:#1e293b - style apply fill:#047857,stroke:#064e3b,color:#fff - style pathfind fill:#0e7490,stroke:#155e75,color:#fff - style pathcomp fill:#0e7490,stroke:#155e75,color:#fff - style txq fill:#047857,stroke:#064e3b,color:#fff - style txqApply fill:#047857,stroke:#064e3b,color:#fff - style feeCalc fill:#047857,stroke:#064e3b,color:#fff - style valFetch fill:#6d28d9,stroke:#4c1d95,color:#fff - style valManifest fill:#6d28d9,stroke:#4c1d95,color:#fff -``` - -**Reading the diagram:** - -- **rpc.request (blue, top)**: The entry point — every traced transaction starts as an RPC call; this root span is the parent of all downstream work. -- **tx.validate and pathfind.request (green/teal, first fork)**: The RPC request fans out into transaction validation and, for cross-currency payments, a PathFinding branch (`pathfind.request` -> `pathfind.compute`). -- **tx.relay -> Peer Spans (teal, middle)**: After validation, the transaction is relayed to peers A, B, and C in parallel; each `peer.send` is a sibling child span showing fan-out across the network. -- **context propagation (dashed arrow)**: The dotted line from `peer.send Peer A` to `consensus.round` represents the trace context crossing a node boundary — the receiving validator picks up the same `trace_id` and continues the trace. -- **consensus.round -> tx.apply -> TxQ Spans (green, lower)**: Once consensus accepts the transaction, it is applied to the ledger; the TxQ spans (`txq.enqueue`, `txq.apply`, `fee.escalate`) capture queue depth and fee escalation behavior. -- **Validator Spans (purple, detached)**: `validator.list.fetch` and `validator.manifest` are independent workflows for UNL management — they run on their own traces and are linked to consensus via Span Links, not parent-child relationships. +The full span inventory (names, attributes, parents as instrumented) is in +[09-data-collection-reference.md §1](./09-data-collection-reference.md#1-opentelemetry-spans). --- diff --git a/OpenTelemetryPlan/09-data-collection-reference.md b/OpenTelemetryPlan/09-data-collection-reference.md index ceb42980c9..ffaa9d307d 100644 --- a/OpenTelemetryPlan/09-data-collection-reference.md +++ b/OpenTelemetryPlan/09-data-collection-reference.md @@ -43,7 +43,7 @@ graph LR BP -->|"OTLP/gRPC :4317"| D SM -->|"span_calls_total
span_duration_ms
(6 dimension labels)"| E - R1 -->|"* gauges
* counters
* histograms"| E + R1 -->|"gauges, counters,
histograms (OTLP)"| E E -->|"Prometheus
data source"| F D -->|"Tempo
data source"| F @@ -732,15 +732,24 @@ sampled at the same instant. ### 3.3 Deployment-Tier Template Variables -Every dashboard carries four filtering template variables (each variable name -matches its Prometheus label), letting one Grafana stack be sliced by tier: +Every dashboard carries seven filtering template variables (each variable name +matches its Prometheus label), letting one Grafana stack be sliced by tier and +by perf-comparison run: -| Variable | Source label | Description | -| ------------------------- | ------------------------ | ------------------------------------------------------------ | -| `$node` | `exported_instance` | Filter by xrpld node instance | -| `$service_name` | `service_name` | Filter by service (`service.name`, e.g. `xrpld`) | -| `$deployment_environment` | `deployment_environment` | Filter by deployment tier (`local` / `test` / `ci` / `prod`) | -| `$xrpl_network_type` | `xrpl_network_type` | Filter by network (`mainnet` / `testnet` / `devnet`) | +| Variable | Source label | Description | +| ------------------------- | ------------------------ | ---------------------------------------------------------------- | +| `$node` | `service_instance_id` | Filter by xrpld node instance | +| `$service_name` | `service_name` | Filter by service (`service.name`, e.g. `xrpld`) | +| `$deployment_environment` | `deployment_environment` | Filter by deployment tier (`local` / `test` / `ci` / `prod`) | +| `$xrpl_network_type` | `xrpl_network_type` | Filter by network (`mainnet` / `testnet` / `devnet` / `perf`) | +| `$xrpl_work_item` | `xrpl_work_item` | Filter by perf-iac work item / ticket (e.g. `RIPD-7455`) | +| `$xrpl_branch` | `xrpl_branch` | Filter by comparison side (`baseline::` / `test:…`) | +| `$xrpl_node_role` | `xrpl_node_role` | Filter by node role (`validator` / `peer`) | + +The last three are populated only during perf-iac comparison runs (stamped as +resource attributes by perf-iac's own alloy pipeline, not the repo collector). +Outside those runs the labels are absent; the filters default to **All**, which +matches series lacking the label so every dashboard still renders. See [telemetry-runbook.md](../docs/telemetry-runbook.md) "Deployment Tiers" for how the tier attributes are set and reach metrics. @@ -869,7 +878,7 @@ Example: 2024-Jan-15 10:30:45.123456 UTC LedgerMaster:NFO trace_id=abc123def456789012345678abcdef01 span_id=0123456789abcdef Validated ledger 42 ``` -- **`trace_id=`** — 32-character lowercase hex trace identifier. Links to the distributed trace in Tempo/Jaeger. +- **`trace_id=`** — 32-character lowercase hex trace identifier. Links to the distributed trace in Tempo. - **`span_id=`** — 16-character lowercase hex span identifier. Identifies the specific span within the trace. - **Only present** when the log is emitted within an active OTel span. Log lines outside of traced code paths have no trace context fields. @@ -942,16 +951,17 @@ async callbacks for new categories. #### Server Info (via OTel MetricsRegistry) -| Prometheus Metric | Type | Labels | Description | -| --------------------------------------------------- | ----- | -------- | -------------------------------------------- | -| `server_info{metric="server_state"}` | Gauge | `metric` | Operating mode (0=DISCONNECTED .. 4=FULL) | -| `server_info{metric="uptime"}` | Gauge | `metric` | Seconds since server start | -| `server_info{metric="peers"}` | Gauge | `metric` | Total connected peers | -| `server_info{metric="validated_ledger_seq"}` | Gauge | `metric` | Validated ledger sequence number | -| `server_info{metric="ledger_current_index"}` | Gauge | `metric` | Current open ledger sequence | -| `server_info{metric="peer_disconnects_resources"}` | Gauge | `metric` | Cumulative resource-related peer disconnects | -| `server_info{metric="last_close_proposers"}` | Gauge | `metric` | Proposers in last closed round | -| `server_info{metric="last_close_converge_time_ms"}` | Gauge | `metric` | Last close convergence time (milliseconds) | +| Prometheus Metric | Type | Labels | Description | +| --------------------------------------------------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `server_info{metric="server_state"}` | Gauge | `metric` | Operating mode (0=DISCONNECTED .. 4=FULL) | +| `server_info{metric="uptime"}` | Gauge | `metric` | Seconds since server start | +| `server_info{metric="peers"}` | Gauge | `metric` | Total connected peers | +| `server_info{metric="validated_ledger_seq"}` | Gauge | `metric` | Validated ledger sequence number | +| `server_info{metric="ledger_current_index"}` | Gauge | `metric` | Current open ledger sequence | +| `server_info{metric="peer_disconnects_resources"}` | Gauge | `metric` | Cumulative resource-related peer disconnects | +| `server_info{metric="last_close_proposers"}` | Gauge | `metric` | Proposers in last closed round | +| `server_info{metric="last_close_converge_time_ms"}` | Gauge | `metric` | Last close convergence time (milliseconds) | +| `server_info{metric="last_close_time"}` | Gauge | `metric` | Network close time of last closed ledger (NetClock secs since XRPL epoch). Query `time() - (value + 946684800)` for last-close age (staleness). Use `1/rate(ledgers_closed_total)` — not a gauge delta — for the close interval | #### Build Info (via OTel MetricsRegistry) diff --git a/OpenTelemetryPlan/OpenTelemetryPlan.md b/OpenTelemetryPlan/OpenTelemetryPlan.md index 0d0100bb8f..b7289d5004 100644 --- a/OpenTelemetryPlan/OpenTelemetryPlan.md +++ b/OpenTelemetryPlan/OpenTelemetryPlan.md @@ -209,7 +209,7 @@ The appendix contains a glossary of OpenTelemetry and xrpld-specific terms, refe ## 9. Data Collection Reference -A single-source-of-truth reference documenting every piece of telemetry data collected by xrpld. Covers all 16 OpenTelemetry spans with their 22 attributes, all StatsD metrics (gauges, counters, histograms, overlay traffic), SpanMetrics-derived Prometheus metrics, and all 10 Grafana dashboards. Includes Jaeger search guides and Prometheus query examples. +A single-source-of-truth reference documenting every piece of telemetry data collected by xrpld. Covers all 16 OpenTelemetry spans with their 22 attributes, all StatsD metrics (gauges, counters, histograms, overlay traffic), SpanMetrics-derived Prometheus metrics, and all 10 Grafana dashboards. Includes Tempo search guides and Prometheus query examples. ➡️ **[View Data Collection Reference](./09-data-collection-reference.md)** diff --git a/OpenTelemetryPlan/Phase11_taskList.md b/OpenTelemetryPlan/Phase11_taskList.md index 07665863af..7429063984 100644 --- a/OpenTelemetryPlan/Phase11_taskList.md +++ b/OpenTelemetryPlan/Phase11_taskList.md @@ -439,6 +439,95 @@ This phase addresses the cross-cutting gap identified during research: **xrpld h --- +## Task 11.12: Alert Rules for External Dashboard Parity Metrics + +> **Source**: [External Dashboard Parity](./06-implementation-phases.md#appendix-external-dashboard-parity) — 18 alert rules ported from the community [xrpl-validator-dashboard](https://github.com/realgrapedrop/xrpl-validator-dashboard). +> +> **Upstream**: Phase 7 Tasks 7.9-7.16 (metrics), Phase 9 Tasks 9.11-9.13 (dashboards). +> **Downstream**: None — terminal task in the parity chain. + +**Objective**: Add Grafana alerting rules for the Phase 7+ parity metrics (validation agreement, validator health, peer quality, state tracking, ledger economy). These complement Task 11.8's `xrpl_*` alerts by covering the `xrpld_*` internal metrics. + +**Critical Group** (8 rules, eval interval 10s): + +| Rule | Condition | For | +| ------------------- | ------------------------------------------------------------- | --- | +| Agreement Below 90% | `xrpld_validation_agreement{metric="agreement_pct_24h"} < 90` | 30s | +| Not Proposing | `xrpld_state_tracking{metric="state_value"} < 6` | 10s | +| Unhealthy State | `xrpld_state_tracking{metric="state_value"} < 4` | 10s | +| Amendment Blocked | `xrpld_validator_health{metric="amendment_blocked"} == 1` | 1m | +| UNL Expiring | `xrpld_validator_health{metric="unl_expiry_days"} < 14` | 1h | +| High IO Latency | `histogram_quantile(0.95, xrpld_ios_latency_bucket) > 50` | 1m | +| High Load Factor | `xrpld_load_factor_metrics{metric="load_factor"} > 1000` | 1m | +| Peer Count Critical | `xrpld_server_info{metric="peers"} < 5` | 1m | + +**Network Group** (3 rules, eval interval 10s): + +| Rule | Condition | For | +| ------------------------- | ----------------------------------------------------------------- | --- | +| Peer Drop >10% | `delta(xrpld_server_info{metric="peers"}[30s]) / ... * 100 < -10` | 30s | +| Peer Drop >30% | Same formula, threshold -30 | 30s | +| P90 Latency + Disconnects | `peer_latency_p90_ms > 500 AND rate(disconnects) > 0` | 2m | + +**Performance Group** (7 rules, eval interval 10s): + +| Rule | Condition | For | +| ------------------- | ------------------------------------------------------------ | --- | +| CPU High | Per-core CPU > 80% (requires node_exporter) | 2m | +| Memory Critical | Memory usage > 90% (requires node_exporter) | 1m | +| Disk Warning | Disk usage > 85% (requires node_exporter) | 2m | +| Job Queue Overflow | `rate(xrpld_jq_trans_overflow_total[5m]) > 0` | 1m | +| Upgrade Recommended | `xrpld_peer_quality{metric="peers_higher_version_pct"} > 60` | 1m | +| TX Rate Drop | Transaction rate dropped > 50% in 5m window | 5m | +| Stale Ledger | `xrpld_ledger_economy{metric="ledger_age_seconds"} > 30` | 1m | + +**Notification channel templates**: Email/SMTP, Discord, Slack, PagerDuty. + +**Key files**: + +- New/extend: `docker/telemetry/grafana/alerting/alert-rules-parity.yaml` +- New: `docker/telemetry/grafana/alerting/contact-points.yaml` (template configs) +- New: `docker/telemetry/grafana/alerting/notification-policies.yaml` + +**Exit Criteria**: + +- [ ] All 18 rules evaluate without errors in Grafana alerting UI +- [ ] Critical rules fire within expected timeframe when conditions are met +- [ ] Notification channel templates are documented (not hard-coded to any service) + +--- + +## Task 11.13: Dual-Datasource Architecture Documentation + +> **Source**: [External Dashboard Parity](./06-implementation-phases.md#appendix-external-dashboard-parity) + +**Objective**: Document the external dashboard's "fast path" pattern as a future optimization for real-time panels. + +**Pattern**: A lightweight Prometheus scrape endpoint (separate from OTLP pipeline) that polls critical metrics every 2-5s, bypassing the 10s OTLP metric reader interval and Prometheus scrape interval. + +**Use case**: Real-time state panels (server state, ledger age, peer count) where 10-15s latency is too slow for operational dashboards. + +**Decision**: Document as a future option, not implement now. The current 10s interval is acceptable for v1. The external dashboard achieves 2-5s freshness by polling RPC directly, which is what the Phase 11 receiver already does. Adding a separate scrape endpoint to xrpld would only be needed if sub-second metric freshness is required from the internal metrics pipeline. + +**What to document**: + +- Architecture comparison: OTLP pipeline (10-15s) vs. direct scrape (2-5s) vs. push gateway +- When to consider: operator feedback indicating 10s is insufficient for alerting SLOs +- How to implement if needed: add `/metrics` HTTP endpoint to xrpld with Prometheus client library +- Trade-offs: additional port, additional dependency, duplication with OTLP metrics + +**Key files**: + +- Update: `OpenTelemetryPlan/09-data-collection-reference.md` (add "Future: Dual-Datasource Architecture" section) +- Update: `docs/telemetry-runbook.md` (add brief note in performance tuning section) + +**Exit Criteria**: + +- [ ] Architecture comparison documented with clear trade-offs +- [ ] Decision rationale recorded (why deferred, when to revisit) + +--- + ## Exit Criteria - [ ] Custom OTel Collector receiver builds and starts without errors @@ -451,3 +540,5 @@ This phase addresses the cross-cutting gap identified during research: **xrpld h - [ ] Receiver handles xrpld restart/unavailability gracefully (no crash, logs warning, retries) - [ ] Documentation complete: receiver README, metric reference, alerting playbook - [ ] Go receiver has unit tests with >80% coverage +- [ ] 18 Grafana alert rules for Phase 7+ parity metrics evaluate correctly (Task 11.12) +- [ ] Dual-datasource architecture documented with trade-offs (Task 11.13) diff --git a/OpenTelemetryPlan/Phase3_taskList.md b/OpenTelemetryPlan/Phase3_taskList.md index 09e63e1f92..c5d3c95251 100644 --- a/OpenTelemetryPlan/Phase3_taskList.md +++ b/OpenTelemetryPlan/Phase3_taskList.md @@ -289,7 +289,7 @@ design. - [ ] `tx.receive` spans carry `peer_version` attribute with a non-empty version string - [ ] Attribute is omitted (not set to empty string) when `getVersion()` returns empty -- [ ] Attribute visible in Jaeger span detail view +- [ ] Attribute visible in Tempo trace detail view --- diff --git a/OpenTelemetryPlan/Phase4_taskList.md b/OpenTelemetryPlan/Phase4_taskList.md index cd84639c93..e83a16262e 100644 --- a/OpenTelemetryPlan/Phase4_taskList.md +++ b/OpenTelemetryPlan/Phase4_taskList.md @@ -379,7 +379,7 @@ Two strategies for cross-node trace correlation, switchable via config: Derive `trace_id = SHA256(previousLedger.id())[0:16]` so all nodes in the same consensus round share the same trace_id without P2P context propagation. -- **Pros**: All nodes appear in the same trace in Tempo/Jaeger automatically. +- **Pros**: All nodes appear in the same trace in Tempo automatically. No collector-side post-processing needed. - **Cons**: Overrides OTel's random trace_id generation; requires custom `IdGenerator` or manual span context construction. @@ -926,7 +926,7 @@ Received messages use **span links** (follows-from), NOT parent-child: - The receiver's processing span links to the sender's context - This preserves each node's independent trace tree -- Cross-node correlation visible via linked traces in Tempo/Jaeger +- Cross-node correlation visible via linked traces in Tempo ## Interaction with Deterministic Trace ID (Strategy A) diff --git a/OpenTelemetryPlan/Phase9_taskList.md b/OpenTelemetryPlan/Phase9_taskList.md index a172cd2e52..818530ef93 100644 --- a/OpenTelemetryPlan/Phase9_taskList.md +++ b/OpenTelemetryPlan/Phase9_taskList.md @@ -127,10 +127,10 @@ These metrics serve multiple external consumer categories identified during rese **What to do**: - Register OTel instruments for PerfLog RPC counters (from `PerfLogImp.cpp` line ~63): - - Counter: `rpc_method_started_total{method=""}` — calls started - - Counter: `rpc_method_finished_total{method=""}` — calls completed - - Counter: `rpc_method_errored_total{method=""}` — calls errored - - Histogram: `rpc_method_duration_us{method=""}` — execution time distribution + - Counter: `xrpld_rpc_method_started_total{method=""}` — calls started + - Counter: `xrpld_rpc_method_finished_total{method=""}` — calls completed + - Counter: `xrpld_rpc_method_errored_total{method=""}` — calls errored + - Histogram: `xrpld_rpc_method_duration_us{method=""}` — execution time distribution - Use OTel `Counter` and `Histogram` instruments with `method` attribute label. @@ -154,11 +154,11 @@ These metrics serve multiple external consumer categories identified during rese **What to do**: - Register OTel instruments for PerfLog job counters: - - Counter: `job_queued_total{job_type=""}` — jobs queued - - Counter: `job_started_total{job_type=""}` — jobs started - - Counter: `job_finished_total{job_type=""}` — jobs completed - - Histogram: `job_queued_duration_us{job_type=""}` — time spent waiting in queue - - Histogram: `job_running_duration_us{job_type=""}` — execution time distribution + - Counter: `xrpld_job_queued_total{job_type=""}` — jobs queued + - Counter: `xrpld_job_started_total{job_type=""}` — jobs started + - Counter: `xrpld_job_finished_total{job_type=""}` — jobs completed + - Histogram: `xrpld_job_queued_duration_us{job_type=""}` — time spent waiting in queue + - Histogram: `xrpld_job_running_duration_us{job_type=""}` — execution time distribution - Hook into PerfLog's existing job tracking alongside Task 9.4. @@ -180,15 +180,15 @@ These metrics serve multiple external consumer categories identified during rese **What to do**: - Register OTel `ObservableGauge` callbacks for `CountedObject` instance counts: - - `object_count{type="Transaction"}` — live Transaction objects - - `object_count{type="Ledger"}` — live Ledger objects - - `object_count{type="NodeObject"}` — live NodeObject instances - - `object_count{type="STTx"}` — serialized transaction objects - - `object_count{type="STLedgerEntry"}` — serialized ledger entries - - `object_count{type="InboundLedger"}` — ledgers being fetched - - `object_count{type="Pathfinder"}` — active pathfinding computations - - `object_count{type="PathRequest"}` — active path requests - - `object_count{type="HashRouterEntry"}` — hash router entries + - `xrpld_object_count{type="Transaction"}` — live Transaction objects + - `xrpld_object_count{type="Ledger"}` — live Ledger objects + - `xrpld_object_count{type="NodeObject"}` — live NodeObject instances + - `xrpld_object_count{type="STTx"}` — serialized transaction objects + - `xrpld_object_count{type="STLedgerEntry"}` — serialized ledger entries + - `xrpld_object_count{type="InboundLedger"}` — ledgers being fetched + - `xrpld_object_count{type="Pathfinder"}` — active pathfinding computations + - `xrpld_object_count{type="PathRequest"}` — active path requests + - `xrpld_object_count{type="HashRouterEntry"}` — hash router entries - The `CountedObject` template already tracks these via atomic counters. The callback just reads the current counts. diff --git a/cmake/XrplDocs.cmake b/cmake/XrplDocs.cmake index 7b3e9b3b30..6f81dcfd5f 100644 --- a/cmake/XrplDocs.cmake +++ b/cmake/XrplDocs.cmake @@ -27,8 +27,12 @@ file( src/*.cpp src/*.md Builds/*.md - *.md ) +# Add only top-level .md files (README, CONTRIBUTING, etc.) without +# recursing into dot-directories like .claude/ whose files are not +# valid Doxygen/CMake sources. +file(GLOB doxygen_top_md CONFIGURE_DEPENDS "*.md") +list(APPEND doxygen_input ${doxygen_top_md}) list(APPEND doxygen_input external/README.md) set(dependencies "${doxygen_input}" "${doxyfile}") diff --git a/docker/telemetry/.env.grafanacloud-alloy.example b/docker/telemetry/.env.grafanacloud-alloy.example new file mode 100644 index 0000000000..a43e09e6d9 --- /dev/null +++ b/docker/telemetry/.env.grafanacloud-alloy.example @@ -0,0 +1,27 @@ +# Grafana Alloy -> Grafana Cloud credentials for docker/telemetry/alloy/config.alloy. +# Copy to `.env.grafanacloud-alloy`, fill in, and source before starting Alloy. +# `.env.grafanacloud-alloy` is gitignored; never commit real tokens or stack ids. +# +# Prometheus values: Grafana Cloud -> Connections -> "Prometheus". +# OTLP values: Grafana Cloud -> Connections -> "OpenTelemetry (OTLP)". + +# --- Host / systemd metrics (prometheus.remote_write) --- +# remote_write push URL, e.g. https://prometheus-prod-XX-.grafana.net/api/prom/push +GRAFANACLOUD_PROM_URL= +# Numeric instance/stack id (Basic-auth username). +GRAFANACLOUD_PROM_USER= +# Access-policy token with metrics:write (Basic-auth password). +GRAFANACLOUD_PROM_KEY= + +# --- xrpld OTLP (otelcol.exporter.otlphttp) --- +# OTLP/HTTP gateway URL including the /otlp path, +# e.g. https://otlp-gateway-prod-.grafana.net/otlp +GRAFANACLOUD_OTLP_URL= +# Numeric instance/stack id (Basic-auth username). +GRAFANACLOUD_OTLP_USER= +# Access-policy token with metrics:write + traces:write (Basic-auth password). +GRAFANACLOUD_OTLP_KEY= + +# --- Per-node label --- +# host label applied to this node's scraped metrics (e.g. the node's hostname). +XRPLD_HOST_LABEL= diff --git a/docker/telemetry/.env.grafanacloud.example b/docker/telemetry/.env.grafanacloud.example new file mode 100644 index 0000000000..53a1a2a7f3 --- /dev/null +++ b/docker/telemetry/.env.grafanacloud.example @@ -0,0 +1,16 @@ +# Grafana Cloud OTLP credentials — copy to `.env.grafanacloud` and fill in. +# Find all three under Grafana Cloud -> Connections -> "OpenTelemetry (OTLP)". +# +# `.env.grafanacloud` is gitignored; never commit real tokens. + +# OTLP/HTTP gateway URL for your stack, including the /otlp path. +# Example: https://otlp-gateway-prod-us-east-0.grafana.net/otlp +GRAFANA_CLOUD_OTLP_ENDPOINT= + +# Numeric instance/stack id shown on the OTLP connection page +# (used as the Basic-auth username). +GRAFANA_CLOUD_INSTANCE_ID= + +# A Cloud Access Policy token with metrics:write, traces:write, logs:write +# (used as the Basic-auth password). +GRAFANA_CLOUD_API_TOKEN= diff --git a/docker/telemetry/.gitignore b/docker/telemetry/.gitignore index 047abc6702..41139c450a 100644 --- a/docker/telemetry/.gitignore +++ b/docker/telemetry/.gitignore @@ -6,3 +6,8 @@ data/ # Keep examples !.env.alerting.example +!.env.grafanacloud.example +!.env.grafanacloud-alloy.example + +# Do not commit grafana cloud versions with default filters +grafanacloud/*.json diff --git a/docker/telemetry/TESTING.md b/docker/telemetry/TESTING.md index 21fb098d27..7cf2b19fa0 100644 --- a/docker/telemetry/TESTING.md +++ b/docker/telemetry/TESTING.md @@ -380,8 +380,8 @@ All 16 production span names instrumented across Phases 2-5: | `rpc.ws_upgrade` | ServerHandler.cpp | 2 | -- | WebSocket upgrade | | `rpc.ws_message` | ServerHandler.cpp | 2 | -- | WebSocket RPC message | | `rpc.process` | ServerHandler.cpp | 2 | -- | RPC processing | -| `rpc.command.` | RPCHandler.cpp | 2 | `xrpl.rpc.command`, `xrpl.rpc.version`, `xrpl.rpc.role` | Any RPC command | -| `tx.process` | NetworkOPs.cpp | 3 | `xrpl.tx.hash`, `xrpl.tx.local`, `xrpl.tx.path` | Submit transaction | +| `rpc.command.` | RPCHandler.cpp | 2 | `command`, `version`, `rpc_role` | Any RPC command | +| `tx.process` | NetworkOPs.cpp | 3 | `xrpl.tx.hash`, `local`, `path` | Submit transaction | | `tx.receive` | PeerImp.cpp | 3 | `xrpl.peer.id` | Peer relays transaction | | `consensus.proposal.send` | RCLConsensus.cpp | 4 | `xrpl.consensus.round` | Consensus proposing phase | | `consensus.ledger_close` | RCLConsensus.cpp | 4 | `xrpl.consensus.ledger.seq`, `xrpl.consensus.mode` | Ledger close event | @@ -392,8 +392,8 @@ All 16 production span names instrumented across Phases 2-5: | `ledger.build` | BuildLedger.cpp | 5 | `xrpl.ledger.seq`, `xrpl.ledger.close_time`, `close_time_correct`, `close_resolution_ms` | Ledger build | | `ledger.validate` | LedgerMaster.cpp | 5 | `xrpl.ledger.seq`, `xrpl.ledger.validations` | Ledger validated | | `ledger.store` | LedgerMaster.cpp | 5 | `xrpl.ledger.seq` | Ledger stored | -| `peer.proposal.receive` | PeerImp.cpp | 5 | `xrpl.peer.id`, `xrpl.peer.proposal.trusted` | Peer sends proposal | -| `peer.validation.receive` | PeerImp.cpp | 5 | `xrpl.peer.id`, `xrpl.peer.validation.trusted` | Peer sends validation | +| `peer.proposal.receive` | PeerImp.cpp | 5 | `xrpl.peer.id`, `proposal_trusted` | Peer sends proposal | +| `peer.validation.receive` | PeerImp.cpp | 5 | `xrpl.peer.id`, `validation_trusted` | Peer sends validation | --- @@ -434,21 +434,21 @@ Base URL: `http://localhost:9090` PROM="http://localhost:9090" # Span call counts (from spanmetrics connector) -curl -s "$PROM/api/v1/query?query=traces_span_metrics_calls_total" | +curl -s "$PROM/api/v1/query?query=span_calls_total" | jq '.data.result[] | {span: .metric.span_name, count: .value[1]}' # Latency histogram -curl -s "$PROM/api/v1/query?query=traces_span_metrics_duration_milliseconds_count" | +curl -s "$PROM/api/v1/query?query=span_duration_milliseconds_count" | jq '.data.result[] | {span: .metric.span_name, count: .value[1]}' # RPC calls by command -curl -s "$PROM/api/v1/query?query=traces_span_metrics_calls_total{span_name=~\"rpc.command.*\"}" | - jq '.data.result[] | {command: .metric["xrpl.rpc.command"], count: .value[1]}' +curl -s "$PROM/api/v1/query?query=span_calls_total{span_name=~\"rpc.command.*\"}" | + jq '.data.result[] | {command: .metric["command"], count: .value[1]}' # Deployment-tier labels present on metrics (set by the collector's # resource/tier processor and promoted via resource_to_telemetry_conversion). # Expect deployment_environment and xrpl_network_type on each series. -curl -s "$PROM/api/v1/query?query=traces_span_metrics_calls_total" | +curl -s "$PROM/api/v1/query?query=span_calls_total" | jq '.data.result[0].metric | {deployment_environment, xrpl_network_type, service_name}' ``` @@ -472,6 +472,65 @@ Pre-configured datasources: --- +## Exporting to Grafana Cloud + +Instead of (or alongside) the local backends, the collector can forward +traces, metrics, and logs to a hosted **Grafana Cloud** stack. This is a +runtime choice layered on top of the base stack — xrpld and the base +`docker-compose.yml` are unchanged. + +### Step 1: Get Grafana Cloud OTLP credentials + +From **Grafana Cloud → Connections → OpenTelemetry (OTLP)**, note the OTLP +gateway endpoint (ends in `/otlp`), the numeric instance id, and an +access-policy token with `metrics:write`, `traces:write`, and `logs:write`. + +### Step 2: Fill in the env file + +```bash +cp docker/telemetry/.env.grafanacloud.example docker/telemetry/.env.grafanacloud +# edit .env.grafanacloud: +# GRAFANA_CLOUD_OTLP_ENDPOINT=https://otlp-gateway-.grafana.net/otlp +# GRAFANA_CLOUD_INSTANCE_ID= +# GRAFANA_CLOUD_API_TOKEN= +``` + +`.env.grafanacloud` is gitignored — never commit real tokens. + +### Step 3: Start the stack with cloud export enabled + +```bash +docker compose -f docker/telemetry/docker-compose.yml \ + -f docker/telemetry/docker-compose.grafanacloud.yaml up -d +``` + +The override swaps the collector onto `otel-collector-config.grafanacloud.yaml`, +which keeps the local Tempo/Prometheus/Loki exporters and adds one +OTLP/HTTP exporter to Grafana Cloud on all three pipelines. Bring the stack +up with just the base file to return to local-only. + +### Step 4: Verify data reaches Grafana Cloud + +After exercising RPC/transaction workflows (Tests 1 or 2), open your Grafana +Cloud instance and confirm: + +- **Traces**: Explore → hosted Tempo datasource → search `{resource.service.name="xrpld"}` +- **Metrics**: Explore → hosted Prometheus/Mimir → query `span_calls_total` +- **Logs**: Explore → hosted Loki → query `{job="xrpld"}` (requires `warning`+ file logging) + +If nothing appears, check the collector logs for auth/export errors: + +```bash +docker compose -f docker/telemetry/docker-compose.yml \ + -f docker/telemetry/docker-compose.grafanacloud.yaml \ + logs otel-collector | grep -iE 'grafanacloud|401|403|export' +``` + +A `401`/`403` means the instance id or token is wrong; a connection error +means the endpoint URL is wrong or missing the `/otlp` path. + +--- + ## Test 3: Log-Trace Correlation (Phase 8) Phase 8 injects `trace_id` and `span_id` into xrpld's log output when @@ -504,10 +563,10 @@ Extract a `trace_id` from the log and verify it exists in Tempo: ```bash TRACE_ID=$(grep -o 'trace_id=[a-f0-9]\{32\}' /path/to/debug.log | head -1 | cut -d= -f2) echo "Checking trace: $TRACE_ID" -curl -s "http://localhost:3200/api/traces/$TRACE_ID" | jq '.data | length' +curl -s "http://localhost:3200/api/traces/$TRACE_ID" | jq '.batches | length' ``` -Expected result: `1` (the trace exists in Tempo). +Expected result: `> 0` (the trace exists in Tempo). ### Step 3: Verify Loki log ingestion diff --git a/docker/telemetry/alloy/config.alloy b/docker/telemetry/alloy/config.alloy new file mode 100644 index 0000000000..788134fedf --- /dev/null +++ b/docker/telemetry/alloy/config.alloy @@ -0,0 +1,279 @@ +// Grafana Alloy collector config for an xrpld node. +// +// WHY THIS FILE EXISTS +// -------------------- +// Some deployments feed telemetry through Grafana Alloy instead of the +// reference OpenTelemetry Collector (otel-collector-config.grafanacloud.yaml). +// xrpld sends OTLP (traces + native beast::insight metrics) to Alloy, which +// forwards to the Grafana Cloud OTLP gateway. That path carries traces and +// native metrics, but on its own produces NO span-derived RED metrics +// (span_calls_total / span_duration_milliseconds_*): those are NOT emitted by +// xrpld -- they are derived by a spanmetrics connector from the spans. Without +// the connector a node is missing from every span-based Grafana dashboard. +// +// This config therefore mirrors the two stages the reference collector has: +// 1. resource tagging (service.name, deployment.environment, xrpl.network.type) +// 2. spanmetrics (derives the RED metrics the dashboards query) +// See docker/telemetry/otel-collector-config.grafanacloud.yaml for the +// authoritative collector equivalent; keep the dimension list in sync with it. +// +// PIPELINE +// +// HOST / SYSTEMD METRICS: +// unix exporter + custom scrape --> relabel(host) --> prometheus.remote_write +// +// xrpld OTLP: +// receiver.otlp ─▶ processor.transform.tier ─┬─▶ connector.spanmetrics ─┐ +// │ │ +// └─▶ processor.batch ◀───────┘ +// │ traces + metrics +// ▼ +// exporter.otlphttp (GC OTLP gateway) +// +// The Grafana Cloud OTLP gateway converts OTLP resource attributes to +// Prometheus labels server-side, so no otelcol.exporter.prometheus is needed. +// +// CONFIGURATION -- NO SECRETS OR TENANT IDS ARE HARD-CODED IN THIS FILE. +// All credentials, endpoints, usernames and the per-node host label are read +// from the environment (same pattern as .env.grafanacloud.example). Copy +// .env.grafanacloud-alloy.example to .env.grafanacloud-alloy, fill it in, and +// source it before starting Alloy. Required variables: +// GRAFANACLOUD_PROM_URL Prometheus remote_write push URL +// GRAFANACLOUD_PROM_USER remote_write basic-auth username (numeric stack id) +// GRAFANACLOUD_PROM_KEY remote_write basic-auth password (access token) +// GRAFANACLOUD_OTLP_URL OTLP/HTTP gateway URL, including the /otlp path +// GRAFANACLOUD_OTLP_USER OTLP basic-auth username (numeric stack id) +// GRAFANACLOUD_OTLP_KEY OTLP basic-auth password (access token) +// XRPLD_HOST_LABEL host label for this node's scraped metrics +// +// PER-DEPLOYMENT EDITS: the deployment.environment and xrpl.network.type tier +// values in otelcol.processor.transform are literals (OTTL cannot read env +// vars) -- edit them to match this node's tier and network. + +logging { + level = "info" +} + +// =========================================================================== +// HOST / SYSTEMD METRICS (Prometheus remote_write path) +// =========================================================================== + +prometheus.remote_write "grafanacloud" { + endpoint { + url = sys.env("GRAFANACLOUD_PROM_URL") + + basic_auth { + username = sys.env("GRAFANACLOUD_PROM_USER") + password = sys.env("GRAFANACLOUD_PROM_KEY") + } + } +} + +prometheus.exporter.unix "default" { + enable_collectors = ["systemd", "textfile"] + systemd { + unit_include = "(rippled|xrpld).*" + } + textfile { + directory = "/usr/local/bin/" + } +} + +prometheus.scrape "system_metrics" { + targets = prometheus.exporter.unix.default.targets + scrape_interval = "15s" + forward_to = [prometheus.relabel.hostname.receiver] +} + +prometheus.scrape "custom_rippled_metrics" { + targets = [{ + __address__ = "localhost:9256", + }] + scrape_interval = "15s" + forward_to = [prometheus.relabel.hostname.receiver] +} + +prometheus.relabel "hostname" { + forward_to = [prometheus.remote_write.grafanacloud.receiver] + + rule { + action = "replace" + source_labels = ["instance"] + replacement = sys.env("XRPLD_HOST_LABEL") + target_label = "host" + } +} + +// =========================================================================== +// xrpld OTLP -> Grafana Cloud (traces + native metrics + derived spanmetrics) +// =========================================================================== + +// Receiver: xrpld sends OTLP/HTTP on 4318 and gRPC on 4317 (localhost only). +otelcol.receiver.otlp "xrpld" { + grpc { + endpoint = "127.0.0.1:4317" + } + http { + endpoint = "127.0.0.1:4318" + } + + output { + // Both signals pass through resource tagging first so they leave with + // identical resource identity. + traces = [otelcol.processor.transform.tier.input] + metrics = [otelcol.processor.transform.tier.input] + } +} + +// Resource tagging (reference: resource/tier + resource/stripsdk). +// * service.name -> "xrpld". Also corrects the service_name="true" +// bool-binding bug on stale binaries: even an un-rebuilt node exports a +// correct service.name once it passes through here. +// * deployment.environment -> set from XRPLD_DEPLOYMENT_ENV (the collector +// IS the environment, so it is authoritative -> upsert). +// * xrpl.network.type -> set only when absent (don't overwrite the node's +// own value). OTTL `where ... == nil` gives insert (not upsert) semantics. +// * telemetry.sdk.* -> deleted (SDK noise). +otelcol.processor.transform "tier" { + error_mode = "ignore" + + // NOTE: statements are OTTL (raw strings) -- River sys.env() does NOT expand + // inside them, and OTTL has no env() converter. The tier values below are + // plain deployment labels (not secrets), so they are literals here. + // PER-DEPLOYMENT EDIT: set deployment.environment and xrpl.network.type to + // match this node's tier and network. + trace_statements { + context = "resource" + statements = [ + `set(attributes["service.name"], "xrpld")`, + `set(attributes["deployment.environment"], "prod")`, + `set(attributes["xrpl.network.type"], "mainnet") where attributes["xrpl.network.type"] == nil`, + `delete_key(attributes, "telemetry.sdk.language")`, + `delete_key(attributes, "telemetry.sdk.name")`, + `delete_key(attributes, "telemetry.sdk.version")`, + ] + } + + metric_statements { + context = "resource" + statements = [ + `set(attributes["service.name"], "xrpld")`, + `set(attributes["deployment.environment"], "prod")`, + `set(attributes["xrpl.network.type"], "mainnet") where attributes["xrpl.network.type"] == nil`, + `delete_key(attributes, "telemetry.sdk.language")`, + `delete_key(attributes, "telemetry.sdk.name")`, + `delete_key(attributes, "telemetry.sdk.version")`, + ] + } + + output { + // Traces fan out: to the batch/gateway path AND into the spanmetrics + // connector so the RED metrics are derived from the same tagged spans. + traces = [ + otelcol.processor.batch.xrpld.input, + otelcol.connector.spanmetrics.xrpld.input, + ] + // Native metrics go straight to the batch/gateway path. + metrics = [otelcol.processor.batch.xrpld.input] + } +} + +// Spanmetrics connector (reference: connectors.spanmetrics, namespace "span"). +// Derives span_calls_total and span_duration_milliseconds_* that every span +// dashboard queries. The dimension list and buckets are copied from the +// reference otel-collector-config.grafanacloud.yaml -- keep them in sync (a +// panel that groups by a dimension missing here renders empty). +otelcol.connector.spanmetrics "xrpld" { + namespace = "span" + + histogram { + unit = "ms" + explicit { + // Buckets MUST stay strictly ascending (the connector binary-searches + // them and silently misbuckets otherwise) and MUST match the two + // otel-collector-config*.yaml lists. Sub-MILLISECOND boundaries exist + // because most xrpld spans sit far below 1ms (tx.preflight means + // ~0.012ms): a 1ms floor put >99.99% of samples in bucket one and pinned + // every p95/p99 at a constant 0.95ms. + // otel-collector-config*.yaml lists. Sub-second boundaries cover + // RPC/tx/ledger spans; 2s-4s resolve second-scale consensus spans + // (consensus.round ~3.9s, consensus.establish ~1.9s) that used to pile + // into one 1s-5s bucket; 10s/30s give the ledger.acquire catch-up tail + // (~17% exceeds 5s) a measurable home so its p95/p99 stop reading +Inf. + buckets = ["0.01ms", "0.05ms", "0.1ms", "0.25ms", "0.5ms", "1ms", "5ms", "10ms", "25ms", "50ms", "100ms", "250ms", "500ms", "1s", "2s", "3s", "4s", "5s", "10s", "30s"] + } + } + + // RPC / transaction dimensions. + dimension { name = "command" } + dimension { name = "rpc_status" } + dimension { name = "tx_type" } + dimension { name = "ter_result" } + dimension { name = "stage" } + dimension { name = "txq_status" } + dimension { name = "load_type" } + dimension { name = "is_batch" } + + // Consensus dimensions. + dimension { name = "consensus_mode" } + dimension { name = "close_time_correct" } + dimension { name = "consensus_state" } + dimension { name = "mode_new" } + dimension { name = "consensus_stalled" } + dimension { name = "consensus_phase" } + dimension { name = "consensus_result" } + + // Overlay / peer dimensions. + dimension { name = "local" } + dimension { name = "suppressed" } + dimension { name = "proposal_trusted" } + dimension { name = "validation_trusted" } + + // gRPC surface dimensions. + dimension { name = "method" } + dimension { name = "grpc_role" } + dimension { name = "grpc_status" } + + // ledger.acquire dimensions. + dimension { name = "outcome" } + dimension { name = "acquire_reason" } + + output { + // Derived span metrics rejoin the metric stream at the batch processor. + metrics = [otelcol.processor.batch.xrpld.input] + } +} + +// Batch traces + metrics (native and span-derived) before export. +otelcol.processor.batch "xrpld" { + timeout = "1s" + send_batch_size = 1024 + + output { + traces = [otelcol.exporter.otlphttp.grafanacloud.input] + metrics = [otelcol.exporter.otlphttp.grafanacloud.input] + } +} + +// Grafana Cloud OTLP gateway auth + exporter. The gateway converts OTLP +// resource attributes to Prometheus labels server-side. +otelcol.auth.basic "grafanacloud" { + username = sys.env("GRAFANACLOUD_OTLP_USER") + password = sys.env("GRAFANACLOUD_OTLP_KEY") +} + +otelcol.exporter.otlphttp "grafanacloud" { + client { + endpoint = sys.env("GRAFANACLOUD_OTLP_URL") + auth = otelcol.auth.basic.grafanacloud.handler + } + retry_on_failure { + enabled = true + max_elapsed_time = "5m" + } + sending_queue { + enabled = true + num_consumers = 4 + queue_size = 1000 + } +} diff --git a/docker/telemetry/docker-compose.grafanacloud.yaml b/docker/telemetry/docker-compose.grafanacloud.yaml new file mode 100644 index 0000000000..b78f188d53 --- /dev/null +++ b/docker/telemetry/docker-compose.grafanacloud.yaml @@ -0,0 +1,28 @@ +# Compose override — enable Grafana Cloud export at runtime. +# +# Layer this on top of the base stack to swap the collector onto the +# Grafana Cloud dual-export config and inject the OTLP credentials. The +# base docker-compose.yml is unchanged, so plain `up` stays local-only. +# +# Usage: +# 1. Put your three Grafana Cloud values in docker/telemetry/.env.grafanacloud +# (template: .env.grafanacloud.example). That file is gitignored. +# 2. Bring the stack up with BOTH compose files: +# docker compose -f docker/telemetry/docker-compose.yml \ +# -f docker/telemetry/docker-compose.grafanacloud.yaml up -d +# 3. To go back to local-only, bring the stack up with just the base file. +# +# Log mount: inherited from the base compose (./data/logs -> /var/log/xrpld). +# Both nodes' xrpld cfgs write to docker/telemetry/data/logs//debug.log, +# so no per-box remap is needed here. + +services: + otel-collector: + volumes: + # Mount the Grafana Cloud collector config over the default path + # (replaces local-only config for this run; file on disk untouched). + - ./otel-collector-config.grafanacloud.yaml:/etc/otel-collector-config.yaml:ro + # Secrets from the env file, injected as container env and + # resolved by the collector via ${env:...}. + env_file: + - .env.grafanacloud diff --git a/docker/telemetry/grafana/dashboards/consensus-health.json b/docker/telemetry/grafana/dashboards/consensus-health.json index 8f1071e21c..cd1e796a33 100644 --- a/docker/telemetry/grafana/dashboards/consensus-health.json +++ b/docker/telemetry/grafana/dashboards/consensus-health.json @@ -1,6 +1,36 @@ { "annotations": { - "list": [] + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + }, + { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": false, + "iconColor": "rgb(70, 70, 70)", + "name": "Annotate perf-iac runs", + "target": { + "limit": 100, + "matchAny": false, + "tags": ["perf-iac"], + "type": "tags" + }, + "type": "tags" + } + ] }, "editable": true, "fiscalYearStartMonth": 0, @@ -10,7 +40,7 @@ "panels": [ { "title": "Validation Send Rate", - "description": "###### What this is:\n*How often this node issues a ledger validation to the network. Each validation asserts the node fully validated one ledger.*\n\n###### How it's computed:\n*Per-second rate of validation events sent, summed per node over a 5-minute window.*\n\n###### Reading it:\n*A flat line at the network's ledger cadence is normal; higher is not better and lower means the node is validating less often.*\n\n###### Healthy range:\n*Roughly one validation per closed ledger (about 0.2-0.3/s on a healthy chain).*\n\n###### Watch for:\n*A drop to zero on a validator means it stopped validating; a value well below the close rate means validation is lagging.*\n\n###### Source:\n[RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`RCLConsensus::Adaptor::createValidationSpan`", + "description": "###### What this is:\n*How often this node issues a ledger validation to the network. Each validation asserts the node fully validated one ledger.*\n\n###### How it's computed:\n*Per-second rate of validation events sent, summed per node over a 5-minute window.*\n\n###### Reading it:\n*A flat line at the network's ledger cadence is normal; higher is not better and lower means the node is validating less often.*\n\n###### Healthy range:\n*Roughly one validation per closed ledger (about 0.2-0.3/s on a healthy chain).*\n\n###### Watch for:\n*A drop to zero on a validator means it stopped validating; a value well below the close rate means validation is lagging.*\n\n###### Keywords:\n- **Ledger validation** *(network event)* \u2014 the second consensus stage where the node confirms a built ledger matches the trusted validator quorum and marks it final.\n- **Ledger close interval** *(network-wide)* \u2014 the network's steady ledger rhythm \u2014 roughly one closed ledger every 3-5 seconds on Mainnet.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`RCLConsensus::Adaptor::createValidationSpan`\n\n###### References:\n[Ledger validation](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure#validation) \u00b7 [Ledger close interval](https://xrpl.org/docs/concepts/ledgers/ledger-close-times) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#ledger-validation)", "type": "stat", "gridPos": { "h": 8, @@ -28,14 +58,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", consensus_mode=~\"$consensus_mode\", span_name=\"consensus.validation.send\"}[$__rate_interval])), \"series\", \"Validations / Sec\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"consensus.validation.send\"}[$__rate_interval])), \"series\", \"Validations / Sec\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: validations/s" }, "overrides": [] @@ -44,7 +75,7 @@ }, { "title": "Consensus Mode Over Time", - "description": "###### What this is:\n*Ledger-close activity split by the node's consensus mode (Proposing, Observing, Wrong Ledger, Switched Ledger).*\n\n###### How it's computed:\n*Per-second rate of ledger-close events grouped by consensus mode, per node, over a 5-minute window.*\n\n###### Reading it:\n*One dominant band is normal; a validator should sit almost entirely in Proposing, a stock node in Observing.*\n\n###### Healthy range:\n*Nearly all activity in a single expected mode.*\n\n###### Watch for:\n*Sustained time in Wrong Ledger or Switched Ledger indicates the node is out of sync or flapping between chains.*\n\n###### Source:\n[RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`RCLConsensus::Adaptor::onClose`", + "description": "###### What this is:\n*Ledger-close activity split by the node's consensus mode (Proposing, Observing, Wrong Ledger, Switched Ledger).*\n\n###### How it's computed:\n*Per-second rate of ledger-close events grouped by consensus mode, per node, over a 5-minute window.*\n\n###### Reading it:\n*One dominant band is normal; a validator should sit almost entirely in Proposing, a stock node in Observing.*\n\n###### Healthy range:\n*Nearly all activity in a single expected mode.*\n\n###### Watch for:\n*Sustained time in Wrong Ledger or Switched Ledger indicates the node is out of sync or flapping between chains.*\n\n###### Keywords:\n- **Ledger close** *(network event)* \u2014 the current open ledger is closed and a new closed ledger is built from the agreed transaction set.\n- **Consensus mode** *(per node)* \u2014 the node's role/health in the current round: Proposing, Observing, Wrong Ledger, or Switched Ledger.\n- **Consensus** *(network event)* \u2014 the protocol by which validators agree on the next ledger's transaction set and close time.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`RCLConsensus::Adaptor::onClose`\n\n###### References:\n[Ledger close](https://xrpl.org/docs/concepts/ledgers/open-closed-validated-ledgers) \u00b7 [Consensus mode](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#ledger-close)", "type": "timeseries", "gridPos": { "h": 8, @@ -62,14 +93,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (consensus_mode, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", consensus_mode=~\"$consensus_mode\", span_name=\"consensus.ledger_close\"}[$__rate_interval])), \"series\", \"$1\", \"consensus_mode\", \"(.*)\")" + "expr": "label_replace(label_join(label_replace(sum by (consensus_mode, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", consensus_mode=~\"$consensus_mode\", span_name=\"consensus.ledger_close\"}[$__rate_interval])), \"series\", \"$1\", \"consensus_mode\", \"(.*)\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: closes/s", "custom": { "axisLabel": "Closes / Sec", @@ -85,7 +117,7 @@ }, { "title": "Consensus Proposals Sent Rate", - "description": "###### What this is:\n*How often this node proposes its candidate transaction set to peers during consensus.*\n\n###### How it's computed:\n*Per-second rate of proposal-send events, summed per node over a 5-minute window.*\n\n###### Reading it:\n*A steady line tracking the ledger cadence is healthy; only proposing (validator) nodes show activity.*\n\n###### Healthy range:\n*Steady output in step with the close rate for a proposing node; zero for a non-proposing node.*\n\n###### Watch for:\n*A proposing validator dropping to zero, or erratic spikes suggesting repeated re-proposals within rounds.*\n\n###### Source:\n[RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`RCLConsensus::Adaptor::propose`", + "description": "###### What this is:\n*How often this node proposes its candidate transaction set to peers during consensus.*\n\n###### How it's computed:\n*Per-second rate of proposal-send events, summed per node over a 5-minute window.*\n\n###### Reading it:\n*A steady line tracking the ledger cadence is healthy; only proposing (validator) nodes show activity.*\n\n###### Healthy range:\n*Steady output in step with the close rate for a proposing node; zero for a non-proposing node.*\n\n###### Watch for:\n*A proposing validator dropping to zero, or erratic spikes suggesting repeated re-proposals within rounds.*\n\n###### Keywords:\n- **Proposal** *(network event)* \u2014 a validator's advertised set of candidate transactions for the next ledger, revised each round.\n- **Consensus** *(network event)* \u2014 the protocol by which validators agree on the next ledger's transaction set and close time.\n- **Ledger close interval** *(network-wide)* \u2014 the network's steady ledger rhythm \u2014 roughly one closed ledger every 3-5 seconds on Mainnet.\n- **Consensus mode** *(per node)* \u2014 the node's role/health in the current round: Proposing, Observing, Wrong Ledger, or Switched Ledger.\n- **Consensus round** *(network event)* \u2014 one propose-and-revise iteration of consensus; several may run before validators converge on a ledger.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`RCLConsensus::Adaptor::propose`\n\n###### References:\n[Proposal](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) \u00b7 [Ledger close interval](https://xrpl.org/docs/concepts/ledgers/ledger-close-times) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#proposal)", "type": "timeseries", "gridPos": { "h": 8, @@ -103,14 +135,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", consensus_mode=~\"$consensus_mode\", span_name=\"consensus.proposal.send\"}[$__rate_interval])), \"series\", \"Proposals / Sec\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"consensus.proposal.send\"}[$__rate_interval])), \"series\", \"Proposals / Sec\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: proposals/s", "custom": { "axisLabel": "Proposals / Sec", @@ -126,7 +159,7 @@ }, { "title": "Accept vs Close Rate", - "description": "###### What this is:\n*Two rates side by side: ledgers accepted after consensus versus ledger closes initiated.*\n\n###### How it's computed:\n*Per-second rate of accept events and of close events, each summed per node over a 5-minute window.*\n\n###### Reading it:\n*The two lines should overlap; read any gap between them as closes that did not finish accepting.*\n\n###### Healthy range:\n*Both lines equal and tracking the network close cadence.*\n\n###### Watch for:\n*A persistent gap where closes exceed accepts points to consensus rounds failing or timing out before acceptance.*\n\n###### Source:\n[RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`RCLConsensus::Adaptor::makeAcceptSpan / RCLConsensus::Adaptor::onClose`", + "description": "###### What this is:\n*Two rates side by side: ledgers accepted after consensus versus ledger closes initiated.*\n\n###### How it's computed:\n*Per-second rate of accept events and of close events, each summed per node over a 5-minute window.*\n\n###### Reading it:\n*The two lines should overlap; read any gap between them as closes that did not finish accepting.*\n\n###### Healthy range:\n*Both lines equal and tracking the network close cadence.*\n\n###### Watch for:\n*A persistent gap where closes exceed accepts points to consensus rounds failing or timing out before acceptance.*\n\n###### Keywords:\n- **Consensus** *(network event)* \u2014 the protocol by which validators agree on the next ledger's transaction set and close time.\n- **Ledger close** *(network event)* \u2014 the current open ledger is closed and a new closed ledger is built from the agreed transaction set.\n- **Ledger close interval** *(network-wide)* \u2014 the network's steady ledger rhythm \u2014 roughly one closed ledger every 3-5 seconds on Mainnet.\n- **Consensus round** *(network event)* \u2014 one propose-and-revise iteration of consensus; several may run before validators converge on a ledger.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`RCLConsensus::Adaptor::makeAcceptSpan / RCLConsensus::Adaptor::onClose`\n\n###### References:\n[Consensus](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) \u00b7 [Ledger close](https://xrpl.org/docs/concepts/ledgers/open-closed-validated-ledgers) \u00b7 [Ledger close interval](https://xrpl.org/docs/concepts/ledgers/ledger-close-times) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#consensus)", "type": "timeseries", "gridPos": { "h": 8, @@ -144,20 +177,22 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"consensus.accept\"}[$__rate_interval])), \"series\", \"Accepts / Sec\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"consensus.accept\"}[$__rate_interval])), \"series\", \"Accepts / Sec\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"consensus.ledger_close\"}[$__rate_interval])), \"series\", \"Closes / Sec\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"consensus.ledger_close\"}[$__rate_interval])), \"series\", \"Closes / Sec\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: events/s", "custom": { "axisLabel": "Events / Sec", @@ -173,7 +208,7 @@ }, { "title": "Validation vs Close Rate", - "description": "###### What this is:\n*Two rates side by side: validations sent versus ledger closes, so you can see whether every closed ledger gets validated.*\n\n###### How it's computed:\n*Per-second rate of validation-send events and of close events, each summed per node over a 5-minute window.*\n\n###### Reading it:\n*The lines should overlap; a validation line below the close line means validation is falling behind.*\n\n###### Healthy range:\n*Both lines equal at the network close cadence.*\n\n###### Watch for:\n*Validations persistently trailing closes, which means the node validates fewer ledgers than it closes.*\n\n###### Source:\n[RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`RCLConsensus::Adaptor::createValidationSpan / RCLConsensus::Adaptor::onClose`", + "description": "###### What this is:\n*Two rates side by side: validations sent versus ledger closes, so you can see whether every closed ledger gets validated.*\n\n###### How it's computed:\n*Per-second rate of validation-send events and of close events, each summed per node over a 5-minute window.*\n\n###### Reading it:\n*The lines should overlap; a validation line below the close line means validation is falling behind.*\n\n###### Healthy range:\n*Both lines equal at the network close cadence.*\n\n###### Watch for:\n*Validations persistently trailing closes, which means the node validates fewer ledgers than it closes.*\n\n###### Keywords:\n- **Validations checked vs sent** *(per node)* \u2014 validations this node received from peers and checked, versus validations it issued itself.\n- **Ledger close** *(network event)* \u2014 the current open ledger is closed and a new closed ledger is built from the agreed transaction set.\n- **Ledger close interval** *(network-wide)* \u2014 the network's steady ledger rhythm \u2014 roughly one closed ledger every 3-5 seconds on Mainnet.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`RCLConsensus::Adaptor::createValidationSpan / RCLConsensus::Adaptor::onClose`\n\n###### References:\n[Validations checked vs sent](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure#validation) \u00b7 [Ledger close](https://xrpl.org/docs/concepts/ledgers/open-closed-validated-ledgers) \u00b7 [Ledger close interval](https://xrpl.org/docs/concepts/ledgers/ledger-close-times) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#validations-checked-vs-sent)", "type": "timeseries", "gridPos": { "h": 8, @@ -191,20 +226,22 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"consensus.validation.send\"}[$__rate_interval])), \"series\", \"Validations / Sec\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"consensus.validation.send\"}[$__rate_interval])), \"series\", \"Validations / Sec\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"consensus.ledger_close\"}[$__rate_interval])), \"series\", \"Closes / Sec\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"consensus.ledger_close\"}[$__rate_interval])), \"series\", \"Closes / Sec\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: events/s", "custom": { "axisLabel": "Events / Sec", @@ -220,7 +257,7 @@ }, { "title": "Time to Reach Consensus (p50/p95)", - "description": "###### What this is:\n*Wall-clock time for the network to agree a ledger, at the median and 95th percentile.*\n\n###### How it's computed:\n*The recorded per-round agreement time is aggregated to its 50th and 95th percentile over the window.*\n\n###### Reading it:\n*Lower is better; the p95 line shows the worst typical rounds while p50 shows the common case.*\n\n###### Healthy range:\n*Roughly 2-4 seconds on mainnet; workload- and network-dependent.*\n\n###### Watch for:\n*Rising percentiles, or a p95 that pulls far above p50, signal slow or contentious rounds under load or poor peer connectivity.*\n\n###### Source:\n[RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`RCLConsensus::Adaptor::makeAcceptSpan`", + "description": "###### What this is:\n*Wall-clock time for the network to agree a ledger, at the median and 95th percentile.*\n\n###### How it's computed:\n*The recorded per-round agreement time is aggregated to its 50th and 95th percentile over the window.*\n\n###### Reading it:\n*Lower is better; the p95 line shows the worst typical rounds while p50 shows the common case.*\n\n###### Healthy range:\n*Roughly 2-4 seconds on mainnet; workload- and network-dependent.*\n\n###### Watch for:\n*Rising percentiles, or a p95 that pulls far above p50, signal slow or contentious rounds under load or poor peer connectivity.*\n\n###### Keywords:\n- **Consensus round** *(network event)* \u2014 one propose-and-revise iteration of consensus; several may run before validators converge on a ledger.\n- **Convergence time** *(network event)* \u2014 the wall-clock time the network took to agree a ledger in a round.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as trace spans and stored in Tempo; the value is computed directly from those raw spans by the panel's TraceQL query (no Prometheus metric involved).*\n\n###### Source:\n[RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`RCLConsensus::Adaptor::makeAcceptSpan`\n\n###### References:\n[Consensus round](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#consensus-round)", "type": "timeseries", "gridPos": { "h": 8, @@ -230,16 +267,16 @@ }, "options": { "tooltip": { - "maxHeight": 600, "mode": "multi", - "sort": "desc" + "sort": "desc", + "maxHeight": 600 } }, "targets": [ { "datasource": { "type": "tempo", - "uid": "tempo" + "uid": "${DS_TEMPO}" }, "queryType": "traceql", "query": "{name=\"consensus.accept\" && resource.service.instance.id=~\"$node\"} | quantile_over_time(span.round_time_ms, .5)", @@ -249,7 +286,7 @@ { "datasource": { "type": "tempo", - "uid": "tempo" + "uid": "${DS_TEMPO}" }, "queryType": "traceql", "refId": "B", @@ -274,7 +311,7 @@ }, { "title": "Average Time to Reach Consensus", - "description": "###### What this is:\n*Mean wall-clock time for the network to agree a ledger.*\n\n###### How it's computed:\n*The recorded per-round agreement time averaged over the window.*\n\n###### Reading it:\n*Lower is better; watch the trend rather than any single point.*\n\n###### Healthy range:\n*Roughly 2-4 seconds on mainnet; workload-dependent.*\n\n###### Watch for:\n*A steady upward drift indicates the network is taking longer to converge, often from load or connectivity problems.*\n\n###### Source:\n[RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`RCLConsensus::Adaptor::makeAcceptSpan`", + "description": "###### What this is:\n*Mean wall-clock time for the network to agree a ledger.*\n\n###### How it's computed:\n*The recorded per-round agreement time averaged over the window.*\n\n###### Reading it:\n*Lower is better; watch the trend rather than any single point.*\n\n###### Healthy range:\n*Roughly 2-4 seconds on mainnet; workload-dependent.*\n\n###### Watch for:\n*A steady upward drift indicates the network is taking longer to converge, often from load or connectivity problems.*\n\n###### Keywords:\n- **Consensus round** *(network event)* \u2014 one propose-and-revise iteration of consensus; several may run before validators converge on a ledger.\n- **Convergence time** *(network event)* \u2014 the wall-clock time the network took to agree a ledger in a round.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as trace spans and stored in Tempo; the value is computed directly from those raw spans by the panel's TraceQL query (no Prometheus metric involved).*\n\n###### Source:\n[RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`RCLConsensus::Adaptor::makeAcceptSpan`\n\n###### References:\n[Consensus round](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#consensus-round)", "type": "timeseries", "gridPos": { "h": 8, @@ -284,16 +321,16 @@ }, "options": { "tooltip": { - "maxHeight": 600, "mode": "multi", - "sort": "desc" + "sort": "desc", + "maxHeight": 600 } }, "targets": [ { "datasource": { "type": "tempo", - "uid": "tempo" + "uid": "${DS_TEMPO}" }, "queryType": "traceql", "query": "{name=\"consensus.accept\" && resource.service.instance.id=~\"$node\"} | avg_over_time(span.round_time_ms)", @@ -318,7 +355,7 @@ }, { "title": "Consensus Rounds per Ledger (Establish Count)", - "description": "###### What this is:\n*How many establish-phase iterations each ledger needed before validators converged.*\n\n###### How it's computed:\n*Ledgers are counted over the window and grouped by their iteration count; each series is one count value.*\n\n###### Reading it:\n*Most weight on the '1' series is ideal; weight shifting to 2, 3 or more shows harder convergence.*\n\n###### Healthy range:\n*The large majority of ledgers converging in one iteration.*\n\n###### Watch for:\n*A growing share of ledgers needing several iterations, indicating disagreement or network stress.*\n\n###### Source:\n[Consensus.h](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/consensus/Consensus.h)\n\n###### Function:\n`Consensus::startEstablishTracing`", + "description": "###### What this is:\n*How many establish-phase iterations each ledger needed before validators converged.*\n\n###### How it's computed:\n*Ledgers are counted over the window and grouped by their iteration count; each series is one count value.*\n\n###### Reading it:\n*Most weight on the '1' series is ideal; weight shifting to 2, 3 or more shows harder convergence.*\n\n###### Healthy range:\n*The large majority of ledgers converging in one iteration.*\n\n###### Watch for:\n*A growing share of ledgers needing several iterations, indicating disagreement or network stress.*\n\n###### Keywords:\n- **Establish phase** *(network event)* \u2014 the consensus phase where validators iterate proposals; the establish count is how many iterations a ledger needed.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as trace spans and stored in Tempo; the value is computed directly from those raw spans by the panel's TraceQL query (no Prometheus metric involved).*\n\n###### Source:\n[Consensus.h](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/consensus/Consensus.h)\n\n###### Function:\n`Consensus::startEstablishTracing`\n\n###### References:\n[Establish phase](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#establish-phase)", "type": "timeseries", "gridPos": { "h": 8, @@ -342,7 +379,7 @@ { "datasource": { "type": "tempo", - "uid": "tempo" + "uid": "${DS_TEMPO}" }, "queryType": "traceql", "refId": "A", @@ -368,7 +405,7 @@ }, { "title": "Previous Round Time per Ledger", - "description": "###### What this is:\n*Duration of the immediately preceding consensus round, at the 95th percentile.*\n\n###### How it's computed:\n*The prior round's recorded duration is aggregated to its 95th percentile over the window.*\n\n###### Reading it:\n*Lower is better; complements the time-to-consensus panels with the previous round's cost.*\n\n###### Healthy range:\n*Comparable to the current time-to-consensus, roughly a few seconds.*\n\n###### Watch for:\n*A rising p95 means recent rounds have been expensive, often preceding a slowdown in ledger cadence.*\n\n###### Source:\n[RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`RCLConsensus::Adaptor::startRoundTracing`", + "description": "###### What this is:\n*Duration of the immediately preceding consensus round, at the 95th percentile.*\n\n###### How it's computed:\n*The prior round's recorded duration is aggregated to its 95th percentile over the window.*\n\n###### Reading it:\n*Lower is better; complements the time-to-consensus panels with the previous round's cost.*\n\n###### Healthy range:\n*Comparable to the current time-to-consensus, roughly a few seconds.*\n\n###### Watch for:\n*A rising p95 means recent rounds have been expensive, often preceding a slowdown in ledger cadence.*\n\n###### Keywords:\n- **Consensus round** *(network event)* \u2014 one propose-and-revise iteration of consensus; several may run before validators converge on a ledger.\n- **Convergence time** *(network event)* \u2014 the wall-clock time the network took to agree a ledger in a round.\n- **Consensus** *(network event)* \u2014 the protocol by which validators agree on the next ledger's transaction set and close time.\n- **Ledger close interval** *(network-wide)* \u2014 the network's steady ledger rhythm \u2014 roughly one closed ledger every 3-5 seconds on Mainnet.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as trace spans and stored in Tempo; the value is computed directly from those raw spans by the panel's TraceQL query (no Prometheus metric involved).*\n\n###### Source:\n[RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`RCLConsensus::Adaptor::startRoundTracing`\n\n###### References:\n[Consensus round](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) \u00b7 [Ledger close interval](https://xrpl.org/docs/concepts/ledgers/ledger-close-times) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#consensus-round)", "type": "timeseries", "gridPos": { "h": 8, @@ -378,16 +415,16 @@ }, "options": { "tooltip": { - "maxHeight": 600, "mode": "multi", - "sort": "desc" + "sort": "desc", + "maxHeight": 600 } }, "targets": [ { "datasource": { "type": "tempo", - "uid": "tempo" + "uid": "${DS_TEMPO}" }, "queryType": "traceql", "query": "{name=\"consensus.round\" && resource.service.instance.id=~\"$node\"} | quantile_over_time(span.previous_round_time_ms, .95)", @@ -412,7 +449,7 @@ }, { "title": "Position Update Duration", - "description": "###### What this is:\n*Time this node spends each round tallying disputes and updating its consensus position, at the 95th percentile.*\n\n###### How it's computed:\n*Per-round position-update durations are aggregated to their 95th percentile over a 5-minute window.*\n\n###### Reading it:\n*Lower is better; short, flat durations are normal.*\n\n###### Healthy range:\n*A few milliseconds under normal load.*\n\n###### Watch for:\n*Sustained high durations point to heavy dispute resolution or slow convergence on close time.*\n\n###### Source:\n[Consensus.h](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/consensus/Consensus.h)\n\n###### Function:\n`Consensus::updateOurPositions`", + "description": "###### What this is:\n*Time this node spends each round tallying disputes and updating its consensus position, at the 95th percentile.*\n\n###### How it's computed:\n*Per-round position-update durations are aggregated to their 95th percentile over a 5-minute window.*\n\n###### Reading it:\n*Lower is better; short, flat durations are normal.*\n\n###### Healthy range:\n*A few milliseconds under normal load.*\n\n###### Watch for:\n*Sustained high durations point to heavy dispute resolution or slow convergence on close time.*\n\n###### Keywords:\n- **Consensus round** *(network event)* \u2014 one propose-and-revise iteration of consensus; several may run before validators converge on a ledger.\n- **Position update** *(per node)* \u2014 the per-round work of tallying disputes and revising the node's own consensus position.\n- **Close time** *(network event)* \u2014 the timestamp validators agree to stamp on a ledger, rounded to a shared resolution.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[Consensus.h](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/consensus/Consensus.h)\n\n###### Function:\n`Consensus::updateOurPositions`\n\n###### References:\n[Consensus round](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) \u00b7 [Close time](https://xrpl.org/docs/concepts/ledgers/ledger-close-times) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#consensus-round)", "type": "timeseries", "gridPos": { "h": 8, @@ -430,14 +467,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(histogram_quantile(0.95, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_duration_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"consensus.update_positions\"}[5m]))), \"series\", \"P95 Update\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(histogram_quantile(0.95, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_duration_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"consensus.update_positions\"}[5m]))), \"series\", \"P95 Update\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "ms", "custom": { "axisLabel": "Duration (ms)", @@ -453,7 +491,7 @@ }, { "title": "Ledger Close Duration", - "description": "###### What this is:\n*Time from when consensus triggers a ledger close to when the close completes, at the 95th percentile.*\n\n###### How it's computed:\n*Per-close durations are aggregated to their 95th percentile per node over a 5-minute window.*\n\n###### Reading it:\n*Lower is better; compare against the round time to see how much of it the close accounts for.*\n\n###### Healthy range:\n*A few to tens of milliseconds; workload-dependent.*\n\n###### Watch for:\n*A rising p95 indicates the close step is becoming a bottleneck, often under heavy transaction volume.*\n\n###### Source:\n[RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`RCLConsensus::Adaptor::onClose`", + "description": "###### What this is:\n*Full consensus round duration (open to accept) at the 95th percentile \u2014 the time a ledger takes to close.*\n\n###### How it's computed:\n*95th percentile of the consensus.round span duration per node over a 5-minute window.*\n\n###### Reading it:\n*Tracks the network close interval (~3-5s on mainnet); lower and steadier is better.*\n\n###### Healthy range:\n*A few seconds, matching the close cadence; workload-dependent on test networks.*\n\n###### Watch for:\n*A rising p95 means rounds are taking longer to converge.*\n\n###### Note:\n*Uses consensus.round, not consensus.ledger_close: the latter span only wraps the sub-millisecond onClose() prologue and is not the ledger close time.*\n\n###### Keywords:\n- **Consensus round** *(network event)* \u2014 one propose-and-revise iteration of consensus; several may run before validators converge on a ledger.\n- **Consensus** *(network event)* \u2014 the protocol by which validators agree on the next ledger's transaction set and close time.\n- **Ledger close interval** *(network-wide)* \u2014 the network's steady ledger rhythm \u2014 roughly one closed ledger every 3-5 seconds on Mainnet.\n- **Convergence time** *(network event)* \u2014 the wall-clock time the network took to agree a ledger in a round.\n- **Ledger close** *(network event)* \u2014 the current open ledger is closed and a new closed ledger is built from the agreed transaction set.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`RCLConsensus::Adaptor::onClose (round span)`\n\n###### References:\n[Consensus round](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) \u00b7 [Ledger close interval](https://xrpl.org/docs/concepts/ledgers/ledger-close-times) \u00b7 [Ledger close](https://xrpl.org/docs/concepts/ledgers/open-closed-validated-ledgers) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#consensus-round)", "type": "timeseries", "gridPos": { "h": 8, @@ -471,14 +509,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(histogram_quantile(0.95, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_duration_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", consensus_mode=~\"$consensus_mode\", span_name=\"consensus.ledger_close\"}[5m]))), \"series\", \"P95 Close Duration\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(histogram_quantile(0.95, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_duration_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", consensus_mode=~\"$consensus_mode\", span_name=\"consensus.round\"}[5m]))), \"series\", \"P95 Close (consensus.round)\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "ms", "custom": { "axisLabel": "Duration (ms)", @@ -494,7 +533,7 @@ }, { "title": "Ledger Apply Duration (doAccept)", - "description": "###### What this is:\n*Time spent applying the agreed transaction set to build the new ledger, at the 95th percentile.*\n\n###### How it's computed:\n*Per-apply durations are aggregated to their 95th percentile per node over a 5-minute window.*\n\n###### Reading it:\n*Lower is better; this is the cost of executing the ledger's transactions.*\n\n###### Healthy range:\n*A few to tens of milliseconds, scaling with the number of transactions in the ledger.*\n\n###### Watch for:\n*A rising p95 alongside high transaction counts signals apply-stage load, whether organic or from a flood of transactions.*\n\n###### Source:\n[RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`RCLConsensus::Adaptor::doAccept`", + "description": "###### What this is:\n*Time spent applying the agreed transaction set to build the new ledger, at the 95th percentile.*\n\n###### How it's computed:\n*Per-apply durations are aggregated to their 95th percentile per node over a 5-minute window.*\n\n###### Reading it:\n*Lower is better; this is the cost of executing the ledger's transactions.*\n\n###### Healthy range:\n*A few to tens of milliseconds, scaling with the number of transactions in the ledger.*\n\n###### Watch for:\n*A rising p95 alongside high transaction counts signals apply-stage load, whether organic or from a flood of transactions.*\n\n###### Keywords:\n- **Transaction apply phase** *(per node)* \u2014 the step that executes the agreed transaction set into the new ledger during a close.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`RCLConsensus::Adaptor::doAccept`\n\n###### References:\n[Transaction apply phase](https://xrpl.org/docs/concepts/ledgers/open-closed-validated-ledgers) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#transaction-apply-phase)", "type": "timeseries", "gridPos": { "h": 8, @@ -505,14 +544,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(histogram_quantile(0.95, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_duration_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", consensus_mode=~\"$consensus_mode\", span_name=\"consensus.accept.apply\"}[5m]))), \"series\", \"P95 Apply Duration\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(histogram_quantile(0.95, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_duration_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"consensus.accept.apply\"}[5m]))), \"series\", \"P95 Apply Duration\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "ms", "custom": { "axisLabel": "Duration (ms)", @@ -535,7 +575,7 @@ }, { "title": "Consensus Accept Duration Heatmap", - "description": "###### What this is:\n*Distribution of how long the accept step takes across rounds, over time.*\n\n###### How it's computed:\n*Accept durations are bucketed by duration band and counted per 5-minute window, drawn as a heatmap.*\n\n###### Reading it:\n*A tight low band is healthy; brighter cells higher up mean more slow rounds.*\n\n###### Healthy range:\n*Most rounds concentrated in the lowest duration bands.*\n\n###### Watch for:\n*A widening or upward-drifting hot band flags outlier rounds that take abnormally long.*\n\n###### Source:\n[RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`RCLConsensus::Adaptor::makeAcceptSpan`", + "description": "###### What this is:\n*Distribution of how long the accept step takes across rounds, over time.*\n\n###### How it's computed:\n*Accept durations are bucketed by duration band and counted per 5-minute window, drawn as a heatmap.*\n\n###### Reading it:\n*A tight low band is healthy; brighter cells higher up mean more slow rounds.*\n\n###### Healthy range:\n*Most rounds concentrated in the lowest duration bands.*\n\n###### Watch for:\n*A widening or upward-drifting hot band flags outlier rounds that take abnormally long.*\n\n###### Keywords:\n- **Consensus round** *(network event)* \u2014 one propose-and-revise iteration of consensus; several may run before validators converge on a ledger.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`RCLConsensus::Adaptor::makeAcceptSpan`\n\n###### References:\n[Consensus round](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#consensus-round)", "type": "heatmap", "gridPos": { "h": 8, @@ -557,7 +597,8 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, "expr": "sum(increase(span_duration_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"consensus.accept\"}[5m])) by (le)", "legendFormat": "{{le}}", @@ -573,7 +614,7 @@ }, { "title": "Close-Time Agreement Rate (Agreed vs Disagreed)", - "description": "###### What this is:\n*How often validators agreed on the ledger close time versus agreed to disagree.*\n\n###### How it's computed:\n*Per-second rate of rounds split into Agreed and Disagreed, per node, over the rate interval.*\n\n###### Reading it:\n*Mostly Agreed is healthy; occasional Disagreed is normal when validator clocks differ slightly.*\n\n###### Healthy range:\n*Overwhelmingly Agreed.*\n\n###### Watch for:\n*A sustained rise in Disagreed points to clock drift or latency spread across the validator set.*\n\n###### Source:\n[RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`RCLConsensus::Adaptor::doAccept`", + "description": "###### What this is:\n*How often validators agreed on the ledger close time versus agreed to disagree.*\n\n###### How it's computed:\n*Per-second rate of rounds split into Agreed and Disagreed, per node, over the rate interval.*\n\n###### Reading it:\n*Mostly Agreed is healthy; occasional Disagreed is normal when validator clocks differ slightly.*\n\n###### Healthy range:\n*Overwhelmingly Agreed.*\n\n###### Watch for:\n*A sustained rise in Disagreed points to clock drift or latency spread across the validator set.*\n\n###### Keywords:\n- **Ledger close** *(network event)* \u2014 the current open ledger is closed and a new closed ledger is built from the agreed transaction set.\n- **Consensus round** *(network event)* \u2014 one propose-and-revise iteration of consensus; several may run before validators converge on a ledger.\n- **Clock drift** *(network event)* \u2014 differences between validators' clocks, which spread their proposed close times.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`RCLConsensus::Adaptor::doAccept`\n\n###### References:\n[Ledger close](https://xrpl.org/docs/concepts/ledgers/open-closed-validated-ledgers) \u00b7 [Consensus round](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) \u00b7 [Clock drift](https://xrpl.org/docs/concepts/ledgers/ledger-close-times) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#ledger-close)", "type": "timeseries", "gridPos": { "h": 8, @@ -584,14 +625,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (close_time_correct, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{span_name=\"consensus.accept.apply\", consensus_mode=~\"$consensus_mode\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])), \"series\", \"$1\", \"close_time_correct\", \"(.*)\")" + "expr": "label_replace(label_join(label_replace(sum by (close_time_correct, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{span_name=\"consensus.accept.apply\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])), \"series\", \"$1\", \"close_time_correct\", \"(.*)\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: rounds/s", "custom": { "axisLabel": "Rounds / Sec", @@ -614,7 +656,7 @@ }, { "title": "Close Time Vote Bins & Resolution", - "description": "###### What this is:\n*Two related values: how many distinct close-time votes were seen, and the current close-time rounding granularity in ms.*\n\n###### How it's computed:\n*Both values are averaged over the window and plotted on dual axes.*\n\n###### Reading it:\n*Fewer vote bins and a fine resolution mean tight agreement; more bins and a coarse resolution mean disagreement.*\n\n###### Healthy range:\n*Few bins with a fine (about 10s) resolution.*\n\n###### Watch for:\n*Rising bins with the resolution widening (toward 120s) shows validators struggling to agree on close time.*\n\n###### Source:\n[RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`RCLConsensus::Adaptor::doAccept`", + "description": "###### What this is:\n*Two related values: how many distinct close-time votes were seen, and the current close-time rounding granularity in ms.*\n\n###### How it's computed:\n*Both values are averaged over the window and plotted on dual axes.*\n\n###### Reading it:\n*Fewer vote bins and a fine resolution mean tight agreement; more bins and a coarse resolution mean disagreement.*\n\n###### Healthy range:\n*Few bins with a fine (about 10s) resolution.*\n\n###### Watch for:\n*Rising bins with the resolution widening (toward 120s) shows validators struggling to agree on close time.*\n\n###### Keywords:\n- **Distinct positions** *(network event)* \u2014 the number of different close-time values validators proposed in a round; one means full agreement.\n- **Close-time resolution** *(network event)* \u2014 the granularity (in seconds) that close times are rounded to; widens when validators disagree.\n- **Close time** *(network event)* \u2014 the timestamp validators agree to stamp on a ledger, rounded to a shared resolution.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as trace spans and stored in Tempo; the value is computed directly from those raw spans by the panel's TraceQL query (no Prometheus metric involved).*\n\n###### Source:\n[RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`RCLConsensus::Adaptor::doAccept`\n\n###### References:\n[Distinct positions](https://xrpl.org/docs/concepts/ledgers/ledger-close-times) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#distinct-positions)", "type": "timeseries", "gridPos": { "h": 8, @@ -685,7 +727,7 @@ { "datasource": { "type": "tempo", - "uid": "tempo" + "uid": "${DS_TEMPO}" }, "queryType": "traceql", "query": "{name=\"consensus.accept.apply\" && resource.service.instance.id=~\"$node\"} | avg_over_time(span.close_time_vote_bins)", @@ -695,7 +737,7 @@ { "datasource": { "type": "tempo", - "uid": "tempo" + "uid": "${DS_TEMPO}" }, "queryType": "traceql", "query": "{name=\"consensus.accept.apply\" && resource.service.instance.id=~\"$node\"} | avg_over_time(span.close_resolution_ms)", @@ -707,7 +749,7 @@ }, { "title": "Close-Time Resolution Change (per Round)", - "description": "###### What this is:\n*Whether the close-time rounding granularity moved Coarser, Finer, or stayed Steady versus the previous round.*\n\n###### How it's computed:\n*Rounds are counted over the window and grouped by direction of change.*\n\n###### Reading it:\n*Mostly Steady is healthy; Coarser means widening bins (more disagreement), Finer means tightening.*\n\n###### Healthy range:\n*Predominantly Steady.*\n\n###### Watch for:\n*Frequent Coarser shifts indicate the network is repeatedly failing to agree on close time.*\n\n###### Source:\n[RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`RCLConsensus::Adaptor::doAccept`", + "description": "###### What this is:\n*Whether the close-time rounding granularity moved Coarser, Finer, or stayed Steady versus the previous round.*\n\n###### How it's computed:\n*Rounds are counted over the window and grouped by direction of change.*\n\n###### Reading it:\n*Mostly Steady is healthy; Coarser means widening bins (more disagreement), Finer means tightening.*\n\n###### Healthy range:\n*Predominantly Steady.*\n\n###### Watch for:\n*Frequent Coarser shifts indicate the network is repeatedly failing to agree on close time.*\n\n###### Keywords:\n- **Close-time resolution** *(network event)* \u2014 the granularity (in seconds) that close times are rounded to; widens when validators disagree.\n- **Consensus round** *(network event)* \u2014 one propose-and-revise iteration of consensus; several may run before validators converge on a ledger.\n- **Close time** *(network event)* \u2014 the timestamp validators agree to stamp on a ledger, rounded to a shared resolution.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as trace spans and stored in Tempo; the value is computed directly from those raw spans by the panel's TraceQL query (no Prometheus metric involved).*\n\n###### Source:\n[RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`RCLConsensus::Adaptor::doAccept`\n\n###### References:\n[Close-time resolution](https://xrpl.org/docs/concepts/ledgers/ledger-close-times) \u00b7 [Consensus round](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#close-time-resolution)", "type": "bargauge", "gridPos": { "h": 8, @@ -737,13 +779,14 @@ }, "legend": { "showLegend": false - } + }, + "tooltip": {} }, "targets": [ { "datasource": { "type": "tempo", - "uid": "tempo" + "uid": "${DS_TEMPO}" }, "queryType": "traceql", "query": "{name=\"consensus.accept.apply\" && resource.service.instance.id=~\"$node\" && span.resolution_direction=~\"$resolution_direction\"} | count_over_time() by (span.resolution_direction)", @@ -778,7 +821,7 @@ }, { "title": "Close-Time Proposal Spread (Distinct Positions per Round)", - "description": "###### What this is:\n*How spread out validators' proposed close times were, as the number of distinct positions per round.*\n\n###### How it's computed:\n*Rounds are counted over the window and grouped by their distinct-position count; each bar is one count value.*\n\n###### Reading it:\n*Weight on '1 distinct position' means everyone agreed; weight on 2 or more means the proposals split.*\n\n###### Healthy range:\n*Most rounds at a single distinct position.*\n\n###### Watch for:\n*A shift toward two or more distinct positions signals growing clock drift or latency across validators.*\n\n###### Source:\n[RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`RCLConsensus::Adaptor::doAccept`", + "description": "###### What this is:\n*How spread out validators' proposed close times were, as the number of distinct positions per round.*\n\n###### How it's computed:\n*Rounds are counted over the window and grouped by their distinct-position count; each bar is one count value.*\n\n###### Reading it:\n*Weight on '1 distinct position' means everyone agreed; weight on 2 or more means the proposals split.*\n\n###### Healthy range:\n*Most rounds at a single distinct position.*\n\n###### Watch for:\n*A shift toward two or more distinct positions signals growing clock drift or latency across validators.*\n\n###### Keywords:\n- **Distinct positions** *(network event)* \u2014 the number of different close-time values validators proposed in a round; one means full agreement.\n- **Consensus round** *(network event)* \u2014 one propose-and-revise iteration of consensus; several may run before validators converge on a ledger.\n- **Proposal** *(network event)* \u2014 a validator's advertised set of candidate transactions for the next ledger, revised each round.\n- **Clock drift** *(network event)* \u2014 differences between validators' clocks, which spread their proposed close times.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as trace spans and stored in Tempo; the value is computed directly from those raw spans by the panel's TraceQL query (no Prometheus metric involved).*\n\n###### Source:\n[RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`RCLConsensus::Adaptor::doAccept`\n\n###### References:\n[Distinct positions](https://xrpl.org/docs/concepts/ledgers/ledger-close-times) \u00b7 [Consensus round](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#distinct-positions)", "type": "bargauge", "gridPos": { "h": 8, @@ -808,13 +851,14 @@ }, "legend": { "showLegend": false - } + }, + "tooltip": {} }, "targets": [ { "datasource": { "type": "tempo", - "uid": "tempo" + "uid": "${DS_TEMPO}" }, "queryType": "traceql", "query": "{name=\"consensus.accept.apply\" && resource.service.instance.id=~\"$node\"} | count_over_time() by (span.close_time_vote_bins)", @@ -826,7 +870,7 @@ }, { "title": "Consensus Outcome Distribution (per Round)", - "description": "###### What this is:\n*How consensus rounds concluded: Agreed, Moved On (partial), Expired (timeout), or No Consensus.*\n\n###### How it's computed:\n*Rounds over the window are counted and grouped by outcome, shown as shares of a pie.*\n\n###### Reading it:\n*An almost fully Agreed pie is healthy; other slices represent degraded outcomes.*\n\n###### Healthy range:\n*Nearly 100 percent Agreed.*\n\n###### Watch for:\n*A growing Moved On or Expired share signals network stress, disagreement, or connectivity loss.*\n\n###### Source:\n[RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`RCLConsensus::Adaptor::makeAcceptSpan`", + "description": "###### What this is:\n*How consensus rounds concluded: Agreed, Moved On (partial), Expired (timeout), or No Consensus.*\n\n###### How it's computed:\n*Rounds over the window are counted and grouped by outcome, shown as shares of a pie.*\n\n###### Reading it:\n*An almost fully Agreed pie is healthy; other slices represent degraded outcomes.*\n\n###### Healthy range:\n*Nearly 100 percent Agreed.*\n\n###### Watch for:\n*A growing Moved On or Expired share signals network stress, disagreement, or connectivity loss.*\n\n###### Keywords:\n- **Consensus round** *(network event)* \u2014 one propose-and-revise iteration of consensus; several may run before validators converge on a ledger.\n- **Consensus outcome** *(network event)* \u2014 how a round concluded: Agreed, Moved On (partial), Expired (timeout), or No Consensus.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`RCLConsensus::Adaptor::makeAcceptSpan`\n\n###### References:\n[Consensus round](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) \u00b7 [Consensus outcome](https://xrpl.org/docs/concepts/consensus-protocol/consensus-principles-and-rules) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#consensus-round)", "type": "piechart", "gridPos": { "h": 8, @@ -848,14 +892,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (consensus_state, xrpl_branch, xrpl_node_role) (increase(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"consensus.accept\", consensus_state!=\"\"}[$__rate_interval])), \"series\", \"$1\", \"consensus_state\", \"(.*)\")" + "expr": "label_replace(label_join(label_replace(sum by (service_instance_id, consensus_state, xrpl_branch, xrpl_node_role) (increase(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"consensus.accept\", consensus_state!=\"\"}[$__rate_interval])), \"series\", \"$1\", \"consensus_state\", \"(.*)\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "short" }, "overrides": [] @@ -864,7 +909,7 @@ }, { "title": "Consensus Failures Over Time", - "description": "###### What this is:\n*Rate of non-normal consensus outcomes (moved-on plus expired) over time.*\n\n###### How it's computed:\n*Per-second rate of the moved-on and expired outcomes, summed per node over a 5-minute window.*\n\n###### Reading it:\n*Flat at zero is healthy; any sustained line is a concern.*\n\n###### Healthy range:\n*Zero under normal operation.*\n\n###### Watch for:\n*Spikes or a persistent nonzero rate indicate consensus instability that can precede ledger stalls or forks.*\n\n###### Source:\n[RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`RCLConsensus::Adaptor::makeAcceptSpan`", + "description": "###### What this is:\n*Rate of non-normal consensus outcomes (moved-on plus expired) over time.*\n\n###### How it's computed:\n*Per-second rate of the moved-on and expired outcomes, summed per node over a 5-minute window.*\n\n###### Reading it:\n*Flat at zero is healthy; any sustained line is a concern.*\n\n###### Healthy range:\n*Zero under normal operation.*\n\n###### Watch for:\n*Spikes or a persistent nonzero rate indicate consensus instability that can precede ledger stalls or forks.*\n\n###### Keywords:\n- **Consensus outcome** *(network event)* \u2014 how a round concluded: Agreed, Moved On (partial), Expired (timeout), or No Consensus.\n- **Consensus** *(network event)* \u2014 the protocol by which validators agree on the next ledger's transaction set and close time.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`RCLConsensus::Adaptor::makeAcceptSpan`\n\n###### References:\n[Consensus outcome](https://xrpl.org/docs/concepts/consensus-protocol/consensus-principles-and-rules) \u00b7 [Consensus](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#consensus-outcome)", "type": "timeseries", "gridPos": { "h": 8, @@ -882,20 +927,22 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"consensus.accept\", consensus_state=\"moved_on\"}[$__rate_interval])), \"series\", \"moved_on\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"consensus.accept\", consensus_state=\"moved_on\"}[$__rate_interval])), \"series\", \"moved_on\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"consensus.accept\", consensus_state=\"expired\"}[$__rate_interval])), \"series\", \"expired\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"consensus.accept\", consensus_state=\"expired\"}[$__rate_interval])), \"series\", \"expired\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: failures/s", "custom": { "axisLabel": "Failures / Sec", @@ -911,7 +958,7 @@ }, { "title": "Consensus Stall Rate", - "description": "###### What this is:\n*Rate at which consensus health checks report a stalled condition versus not stalled.*\n\n###### How it's computed:\n*Per-second rate of consensus checks split by stalled flag, summed per node over a 5-minute window.*\n\n###### Reading it:\n*The Not Stalled line should carry all the weight; any Stalled line is an early warning.*\n\n###### Healthy range:\n*Stalled at zero.*\n\n###### Watch for:\n*A nonzero stalled rate surfaces stalls before they show up as validated-ledger-age alarms.*\n\n###### Source:\n[Consensus.h](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/consensus/Consensus.h)\n\n###### Function:\n`Consensus::haveConsensus`", + "description": "###### What this is:\n*Rate at which consensus health checks report a stalled condition versus not stalled.*\n\n###### How it's computed:\n*Per-second rate of consensus checks split by stalled flag, summed per node over a 5-minute window.*\n\n###### Reading it:\n*The Not Stalled line should carry all the weight; any Stalled line is an early warning.*\n\n###### Healthy range:\n*Stalled at zero.*\n\n###### Watch for:\n*A nonzero stalled rate surfaces stalls before they show up as validated-ledger-age alarms.*\n\n###### Keywords:\n- **Consensus** *(network event)* \u2014 the protocol by which validators agree on the next ledger's transaction set and close time.\n- **Consensus stall** *(per node)* \u2014 a health check reporting that consensus is not making forward progress.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[Consensus.h](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/consensus/Consensus.h)\n\n###### Function:\n`Consensus::haveConsensus`\n\n###### References:\n[Consensus](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) \u00b7 [Consensus stall](https://xrpl.org/docs/concepts/consensus-protocol/consensus-principles-and-rules) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#consensus)", "type": "timeseries", "gridPos": { "h": 8, @@ -929,20 +976,22 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"consensus.check\", consensus_stalled=\"true\"}[$__rate_interval])), \"series\", \"Stalled\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"consensus.check\", consensus_stalled=\"true\"}[$__rate_interval])), \"series\", \"Stalled\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"consensus.check\", consensus_stalled=\"false\"}[$__rate_interval])), \"series\", \"Not Stalled\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"consensus.check\", consensus_stalled=\"false\"}[$__rate_interval])), \"series\", \"Not Stalled\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: checks/s", "custom": { "axisLabel": "Checks / Sec", @@ -958,7 +1007,7 @@ }, { "title": "Consensus Mode-Change Rate by Target Mode", - "description": "###### What this is:\n*How often the node switches consensus mode, broken down by the mode it switched into.*\n\n###### How it's computed:\n*Per-second rate of mode-change events grouped by target mode, per node, over a 5-minute window.*\n\n###### Reading it:\n*Near-zero is healthy; a stable node rarely changes mode.*\n\n###### Healthy range:\n*Essentially zero mode changes in steady state.*\n\n###### Watch for:\n*Frequent switches into Wrong Ledger or Switched Ledger mark an unstable node at risk of forking.*\n\n###### Source:\n[RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`RCLConsensus::Adaptor::onModeChange`", + "description": "###### What this is:\n*How often the node switches consensus mode, broken down by the mode it switched into.*\n\n###### How it's computed:\n*Per-second rate of mode-change events grouped by target mode, per node, over a 5-minute window.*\n\n###### Reading it:\n*Near-zero is healthy; a stable node rarely changes mode.*\n\n###### Healthy range:\n*Essentially zero mode changes in steady state.*\n\n###### Watch for:\n*Frequent switches into Wrong Ledger or Switched Ledger mark an unstable node at risk of forking.*\n\n###### Keywords:\n- **Consensus mode** *(per node)* \u2014 the node's role/health in the current round: Proposing, Observing, Wrong Ledger, or Switched Ledger.\n- **Fork** *(network-wide)* \u2014 when nodes validate divergent ledger chains instead of a single agreed history.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`RCLConsensus::Adaptor::onModeChange`\n\n###### References:\n[Consensus mode](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) \u00b7 [Fork](https://xrpl.org/docs/concepts/consensus-protocol) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#consensus-mode)", "type": "timeseries", "gridPos": { "h": 8, @@ -976,14 +1025,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (mode_new, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"consensus.mode_change\"}[$__rate_interval])), \"series\", \"$1\", \"mode_new\", \"(.*)\")" + "expr": "label_replace(label_join(label_replace(sum by (mode_new, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"consensus.mode_change\"}[$__rate_interval])), \"series\", \"$1\", \"mode_new\", \"(.*)\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: changes/s", "custom": { "axisLabel": "Changes / Sec", @@ -999,7 +1049,7 @@ }, { "title": "Ledger History Mismatch Rate by Reason", - "description": "###### What this is:\n*Rate of built-versus-validated ledger mismatches, broken down by why they diverged.*\n\n###### How it's computed:\n*Per-second rate of mismatch events grouped by reason, per node, over a 5-minute window.*\n\n###### Reading it:\n*Flat at zero is healthy; the reason label tells you the nature of any divergence.*\n\n###### Healthy range:\n*Zero under normal operation.*\n\n###### Watch for:\n*Any sustained rate marks a fork; the reason distinguishes close-time disagreement, sync drift, and transaction-processing differences.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`incrementLedgerHistoryMismatch`", + "description": "###### What this is:\n*Rate of built-versus-validated ledger mismatches, broken down by why they diverged.*\n\n###### How it's computed:\n*Per-second rate of mismatch events grouped by reason, per node, over a 5-minute window.*\n\n###### Reading it:\n*Flat at zero is healthy; the reason label tells you the nature of any divergence.*\n\n###### Healthy range:\n*Zero under normal operation.*\n\n###### Watch for:\n*Any sustained rate marks a fork; the reason distinguishes close-time disagreement, sync drift, and transaction-processing differences.*\n\n###### Keywords:\n- **Ledger history mismatch** *(per node)* \u2014 when a locally built ledger's hash does not match the network-validated hash.\n- **Fork** *(network-wide)* \u2014 when nodes validate divergent ledger chains instead of a single agreed history.\n- **Close time** *(network event)* \u2014 the timestamp validators agree to stamp on a ledger, rounded to a shared resolution.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`incrementLedgerHistoryMismatch`\n\n###### References:\n[Fork](https://xrpl.org/docs/concepts/consensus-protocol) \u00b7 [Close time](https://xrpl.org/docs/concepts/ledgers/ledger-close-times) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#ledger-history-mismatch)", "type": "timeseries", "gridPos": { "h": 8, @@ -1017,14 +1067,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (reason, service_instance_id, xrpl_branch, xrpl_node_role) (rate(ledger_history_mismatch_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])), \"series\", \"$1\", \"reason\", \"(.*)\")" + "expr": "label_replace(label_join(label_replace(sum by (reason, service_instance_id, xrpl_branch, xrpl_node_role) (rate(ledger_history_mismatch_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])), \"series\", \"$1\", \"reason\", \"(.*)\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: mismatches/s", "custom": { "axisLabel": "Mismatches / Sec", @@ -1043,6 +1094,32 @@ "tags": ["consensus"], "templating": { "list": [ + { + "name": "DS_PROMETHEUS", + "type": "datasource", + "label": "Prometheus", + "query": "prometheus", + "regex": "", + "current": {}, + "hide": 0, + "refresh": 1, + "includeAll": false, + "multi": false, + "options": [] + }, + { + "name": "DS_TEMPO", + "type": "datasource", + "label": "Tempo", + "query": "tempo", + "regex": "", + "current": {}, + "hide": 0, + "refresh": 1, + "includeAll": false, + "multi": false, + "options": [] + }, { "name": "service_name", "label": "Service Name", @@ -1051,7 +1128,7 @@ "query": "label_values(service_name)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -1071,7 +1148,7 @@ "query": "label_values(deployment_environment)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -1091,7 +1168,7 @@ "query": "label_values(xrpl_network_type)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -1171,7 +1248,7 @@ "query": "label_values(target_info, service_instance_id)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -1191,7 +1268,7 @@ "query": "label_values(span_calls_total{span_name=\"consensus.ledger_close\"}, consensus_mode)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -1276,5 +1353,6 @@ }, "title": "Consensus Health", "uid": "consensus-health", - "description": "What this shows: Consensus health for XRPL nodes: how reliably and quickly the network agrees each ledger, and where agreement breaks down.\nUse it to: Spot stalled or slow consensus rounds and pinpoint the phase where agreement is failing." + "description": "What this shows: Consensus health for XRPL nodes: how reliably and quickly the network agrees each ledger, and where agreement breaks down. \u2014 Use it to: Spot stalled or slow consensus rounds and pinpoint the phase where agreement is failing.", + "refresh": "30s" } diff --git a/docker/telemetry/grafana/dashboards/fee-market.json b/docker/telemetry/grafana/dashboards/fee-market.json index b8ff971de7..8d1cc6bfd8 100644 --- a/docker/telemetry/grafana/dashboards/fee-market.json +++ b/docker/telemetry/grafana/dashboards/fee-market.json @@ -1,8 +1,38 @@ { "annotations": { - "list": [] + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + }, + { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": false, + "iconColor": "rgb(70, 70, 70)", + "name": "Annotate perf-iac runs", + "target": { + "limit": 100, + "matchAny": false, + "tags": ["perf-iac"], + "type": "tags" + }, + "type": "tags" + } + ] }, - "description": "What this shows: Transaction queue depth and capacity, fee-escalation levels, and load-factor breakdown that together set the minimum fee to enter the open ledger.\nUse it to: Understand why the minimum transaction fee is rising and how full the open ledger and queue are.", + "description": "What this shows: Transaction queue depth and capacity, fee-escalation levels, and load-factor breakdown that together set the minimum fee to enter the open ledger. \u2014 Use it to: Understand why the minimum transaction fee is rising and how full the open ledger and queue are.", "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 1, @@ -11,7 +41,7 @@ "panels": [ { "title": "Transaction Queue Depth", - "description": "###### What this is:\n*Transactions currently waiting in the transaction queue versus the queue's maximum capacity.*\n\n###### How it's computed:\n*Instantaneous gauge readings of current queue count and configured max size.*\n\n###### Reading it:\n*Queue depth well below capacity is normal; depth approaching capacity means the node is saturating.*\n\n###### Healthy range:\n*Depth near 0 in quiet periods; workload-dependent under load.*\n\n###### Watch for:\n*Depth pinned at capacity for sustained periods, which signals demand exceeding throughput or a fee-spam burst.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerTxqGauge`", + "description": "###### What this is:\n*Transactions currently waiting in the transaction queue versus the queue's maximum capacity.*\n\n###### How it's computed:\n*Instantaneous gauge readings of current queue count and configured max size.*\n\n###### Reading it:\n*Queue depth well below capacity is normal; depth approaching capacity means the node is saturating.*\n\n###### Healthy range:\n*Depth near 0 in quiet periods; workload-dependent under load.*\n\n###### Watch for:\n*Depth pinned at capacity for sustained periods, which signals demand exceeding throughput or a fee-spam burst.*\n\n###### Keywords:\n- **Transaction queue (TxQ)** *(per node)* \u2014 holds transactions that meet local cost but not the open-ledger cost, to include in a later ledger.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerTxqGauge`\n\n###### References:\n[Transaction queue (TxQ)](https://xrpl.org/docs/concepts/transactions/transaction-queue) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#transaction-queue-txq)", "type": "timeseries", "gridPos": { "h": 8, @@ -29,20 +59,22 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(txq_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"txq_count\"}, \"series\", \"Queue Depth\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(txq_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"txq_count\"}, \"series\", \"Queue Depth\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(txq_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"txq_max_size\"}, \"series\", \"Max Capacity\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(txq_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"txq_max_size\"}, \"series\", \"Max Capacity\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "short", "custom": { "axisLabel": "Transactions", @@ -60,7 +92,7 @@ }, { "title": "Transactions Per Ledger", - "description": "###### What this is:\n*Transactions already placed in the current open ledger versus the expected per-ledger target.*\n\n###### How it's computed:\n*Instantaneous gauge readings of in-ledger count and the target count that governs fee escalation.*\n\n###### Reading it:\n*Staying at or below the expected target is normal; exceeding it triggers open-ledger fee escalation.*\n\n###### Healthy range:\n*At or under the expected per-ledger target.*\n\n###### Watch for:\n*In-ledger count persistently above target, indicating sustained congestion pushing fees up.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerTxqGauge`", + "description": "###### What this is:\n*Transactions already placed in the current open ledger versus the expected per-ledger target.*\n\n###### How it's computed:\n*Instantaneous gauge readings of in-ledger count and the target count that governs fee escalation.*\n\n###### Reading it:\n*Staying at or below the expected target is normal; exceeding it triggers open-ledger fee escalation.*\n\n###### Healthy range:\n*At or under the expected per-ledger target.*\n\n###### Watch for:\n*In-ledger count persistently above target, indicating sustained congestion pushing fees up.*\n\n###### Keywords:\n- **Open ledger** *(per node)* \u2014 the temporary workspace ledger where incoming transactions are provisionally applied before a close.\n- **In-ledger vs target count** *(per node)* \u2014 transactions already in the open ledger versus the soft target that triggers fee escalation.\n- **Fee escalation** *(per node)* \u2014 the exponential rise in the open-ledger cost once the ledger exceeds its soft transaction target.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerTxqGauge`\n\n###### References:\n[Open ledger](https://xrpl.org/docs/concepts/ledgers/open-closed-validated-ledgers) \u00b7 [In-ledger vs target count](https://xrpl.org/docs/concepts/transactions/transaction-cost#open-ledger-cost) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#open-ledger)", "type": "timeseries", "gridPos": { "h": 8, @@ -78,20 +110,22 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(txq_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"txq_in_ledger\"}, \"series\", \"In Ledger\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(txq_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"txq_in_ledger\"}, \"series\", \"In Ledger\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(txq_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"txq_per_ledger\"}, \"series\", \"Expected Per Ledger\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(txq_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"txq_per_ledger\"}, \"series\", \"Expected Per Ledger\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "short", "custom": { "axisLabel": "Transactions", @@ -109,7 +143,7 @@ }, { "title": "Fee Escalation Levels", - "description": "###### What this is:\n*The fee levels that govern queue admission: reference (baseline), minimum processing, median, and open-ledger levels.*\n\n###### How it's computed:\n*Instantaneous gauge readings of each fee level, shown on a log scale.*\n\n###### Reading it:\n*Open-ledger level near the reference level means cheap entry; a large gap above reference means escalation is active.*\n\n###### Healthy range:\n*Open-ledger level at or near reference during normal traffic.*\n\n###### Watch for:\n*Open-ledger level spiking far above reference, the hallmark of congestion or a fee-bidding war.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerTxqGauge`", + "description": "###### What this is:\n*The fee levels that govern queue admission: reference (baseline), minimum processing, median, and open-ledger levels.*\n\n###### How it's computed:\n*Instantaneous gauge readings of each fee level, shown on a log scale.*\n\n###### Reading it:\n*Open-ledger level near the reference level means cheap entry; a large gap above reference means escalation is active.*\n\n###### Healthy range:\n*Open-ledger level at or near reference during normal traffic.*\n\n###### Watch for:\n*Open-ledger level spiking far above reference, the hallmark of congestion or a fee-bidding war.*\n\n###### Keywords:\n- **Queue admission rejection** *(per node)* \u2014 a transaction refused entry to the queue, e.g. queue_full when the queue is at capacity.\n- **Fee levels** *(per node)* \u2014 cost thresholds governing queue admission: reference (baseline), minimum, median, and open-ledger.\n- **Open ledger** *(per node)* \u2014 the temporary workspace ledger where incoming transactions are provisionally applied before a close.\n- **Fee escalation** *(per node)* \u2014 the exponential rise in the open-ledger cost once the ledger exceeds its soft transaction target.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerTxqGauge`\n\n###### References:\n[Queue admission rejection](https://xrpl.org/docs/concepts/transactions/transaction-queue) \u00b7 [Fee levels](https://xrpl.org/docs/concepts/transactions/transaction-cost#fee-levels) \u00b7 [Open ledger](https://xrpl.org/docs/concepts/ledgers/open-closed-validated-ledgers) \u00b7 [Fee escalation](https://xrpl.org/docs/concepts/transactions/transaction-cost#open-ledger-cost) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#queue-admission-rejection)", "type": "timeseries", "gridPos": { "h": 8, @@ -127,32 +161,36 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(txq_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"txq_reference_fee_level\"}, \"series\", \"Reference Fee Level\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(txq_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"txq_reference_fee_level\"}, \"series\", \"Reference Fee Level\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(txq_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"txq_min_processing_fee_level\"}, \"series\", \"Min Processing Fee Level\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(txq_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"txq_min_processing_fee_level\"}, \"series\", \"Min Processing Fee Level\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(txq_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"txq_med_fee_level\"}, \"series\", \"Median Fee Level\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(txq_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"txq_med_fee_level\"}, \"series\", \"Median Fee Level\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(txq_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"txq_open_ledger_fee_level\"}, \"series\", \"Open Ledger Fee Level\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(txq_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"txq_open_ledger_fee_level\"}, \"series\", \"Open Ledger Fee Level\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "short", "custom": { "axisLabel": "Fee Level", @@ -174,7 +212,7 @@ }, { "title": "Load Factor Breakdown", - "description": "###### What this is:\n*The combined load factor and its server, fee-escalation, and fee-queue contributors as unitless fee multipliers (1.0 = no load).*\n\n###### How it's computed:\n*Instantaneous gauge readings of each load-factor component.*\n\n###### Reading it:\n*Values at 1.0 mean base fees; higher values raise the fee to transact.*\n\n###### Healthy range:\n*Around 1.0 under normal conditions.*\n\n###### Watch for:\n*Combined factor climbing well above 1.0, showing the node is charging premium fees due to congestion or overload.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerLoadFactorGauge`", + "description": "###### What this is:\n*The combined load factor and its server, fee-escalation, and fee-queue contributors as unitless fee multipliers (1.0 = no load).*\n\n###### How it's computed:\n*Instantaneous gauge readings of each load-factor component.*\n\n###### Reading it:\n*Values at 1.0 mean base fees; higher values raise the fee to transact.*\n\n###### Healthy range:\n*Around 1.0 under normal conditions.*\n\n###### Watch for:\n*Combined factor climbing well above 1.0, showing the node is charging premium fees due to congestion or overload.*\n\n###### Keywords:\n- **Load factor** *(per node)* \u2014 a unitless multiplier (1.0 = no load) that scales the base transaction cost as the node comes under load.\n- **Fee escalation** *(per node)* \u2014 the exponential rise in the open-ledger cost once the ledger exceeds its soft transaction target.\n- **Transaction cost** *(network-wide)* \u2014 the XRP a transaction destroys to be processed; scales up with load to deter spam.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerLoadFactorGauge`\n\n###### References:\n[Load factor](https://xrpl.org/docs/concepts/transactions/transaction-cost#local-load-cost) \u00b7 [Fee escalation](https://xrpl.org/docs/concepts/transactions/transaction-cost#open-ledger-cost) \u00b7 [Transaction cost](https://xrpl.org/docs/concepts/transactions/transaction-cost) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#load-factor)", "type": "timeseries", "gridPos": { "h": 8, @@ -192,32 +230,36 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(load_factor_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"load_factor\"}, \"series\", \"Combined Load Factor\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(load_factor_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"load_factor\"}, \"series\", \"Combined Load Factor\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(load_factor_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"load_factor_server\"}, \"series\", \"Server\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(load_factor_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"load_factor_server\"}, \"series\", \"Server\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(load_factor_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"load_factor_fee_escalation\"}, \"series\", \"Fee Escalation\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(load_factor_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"load_factor_fee_escalation\"}, \"series\", \"Fee Escalation\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(load_factor_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"load_factor_fee_queue\"}, \"series\", \"Fee Queue\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(load_factor_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"load_factor_fee_queue\"}, \"series\", \"Fee Queue\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "short", "custom": { "axisLabel": "Multiplier", @@ -251,7 +293,7 @@ }, { "title": "Load Factor Components", - "description": "###### What this is:\n*The individual load-factor inputs, local server load, network load, and cluster load, as unitless multipliers.*\n\n###### How it's computed:\n*Instantaneous gauge readings of each component.*\n\n###### Reading it:\n*All at 1.0 means no load pressure from any source; a raised component identifies where load originates.*\n\n###### Healthy range:\n*Around 1.0 for each component.*\n\n###### Watch for:\n*A single component rising sharply, which pinpoints whether the pressure is local, network-wide, or cluster-driven.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerLoadFactorGauge`", + "description": "###### What this is:\n*The individual load-factor inputs, local server load, network load, and cluster load, as unitless multipliers.*\n\n###### How it's computed:\n*Instantaneous gauge readings of each component.*\n\n###### Reading it:\n*All at 1.0 means no load pressure from any source; a raised component identifies where load originates.*\n\n###### Healthy range:\n*Around 1.0 for each component.*\n\n###### Watch for:\n*A single component rising sharply, which pinpoints whether the pressure is local, network-wide, or cluster-driven.*\n\n###### Keywords:\n- **Load factor** *(per node)* \u2014 a unitless multiplier (1.0 = no load) that scales the base transaction cost as the node comes under load.\n- **Cluster** *(cluster-wide)* \u2014 a group of trusted co-operated nodes that share load information and skip some verification.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerLoadFactorGauge`\n\n###### References:\n[Load factor](https://xrpl.org/docs/concepts/transactions/transaction-cost#local-load-cost) \u00b7 [Cluster](https://xrpl.org/docs/concepts/networks-and-servers/clustering) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#load-factor)", "type": "timeseries", "gridPos": { "h": 8, @@ -269,26 +311,29 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(load_factor_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"load_factor_local\"}, \"series\", \"Local\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(load_factor_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"load_factor_local\"}, \"series\", \"Local\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(load_factor_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"load_factor_net\"}, \"series\", \"Network\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(load_factor_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"load_factor_net\"}, \"series\", \"Network\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(load_factor_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"load_factor_cluster\"}, \"series\", \"Cluster\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(load_factor_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"load_factor_cluster\"}, \"series\", \"Cluster\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "short", "custom": { "axisLabel": "Multiplier", @@ -306,7 +351,7 @@ }, { "title": "Queue Abandonment Rate (Expired)", - "description": "###### What this is:\n*Transactions dropped from the queue because their last-ledger deadline passed before they could be included.*\n\n###### How it's computed:\n*Per-second rate of the cumulative expired-transaction counter over the dashboard's rate interval.*\n\n###### Reading it:\n*Near zero is healthy; a rising rate means submitters under-bid the escalating fee and their transactions timed out.*\n\n###### Healthy range:\n*Near 0 expirations per second.*\n\n###### Watch for:\n*Sustained expiry rate, a demand-frustration signal often coinciding with fee spikes or spam that crowds out honest traffic.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`incrementTxqExpired (caller TxQ.cpp)`", + "description": "###### What this is:\n*Transactions dropped from the queue because their last-ledger deadline passed before they could be included.*\n\n###### How it's computed:\n*Per-second rate of the cumulative expired-transaction counter over the dashboard's rate interval.*\n\n###### Reading it:\n*Near zero is healthy; a rising rate means submitters under-bid the escalating fee and their transactions timed out.*\n\n###### Healthy range:\n*Near 0 expirations per second.*\n\n###### Watch for:\n*Sustained expiry rate, a demand-frustration signal often coinciding with fee spikes or spam that crowds out honest traffic.*\n\n###### Keywords:\n- **Queue admission rejection** *(per node)* \u2014 a transaction refused entry to the queue, e.g. queue_full when the queue is at capacity.\n- **Queue expiry / abandonment** *(per node)* \u2014 removing a queued transaction whose LastLedgerSequence deadline passed before inclusion.\n- **Fee escalation** *(per node)* \u2014 the exponential rise in the open-ledger cost once the ledger exceeds its soft transaction target.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`incrementTxqExpired (caller TxQ.cpp)`\n\n###### References:\n[Queue admission rejection](https://xrpl.org/docs/concepts/transactions/transaction-queue) \u00b7 [Queue expiry / abandonment](https://xrpl.org/docs/concepts/transactions/reliable-transaction-submission) \u00b7 [Fee escalation](https://xrpl.org/docs/concepts/transactions/transaction-cost#open-ledger-cost) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#queue-admission-rejection)", "type": "timeseries", "gridPos": { "h": 8, @@ -324,14 +369,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(txq_expired_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])), \"series\", \"Expired / Sec\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(txq_expired_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])), \"series\", \"Expired / Sec\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: transactions/s", "custom": { "axisLabel": "Transactions / Sec", @@ -346,7 +392,7 @@ }, { "title": "Queue Admission Rejections (Dropped)", - "description": "###### What this is:\n*Transactions refused entry to the queue, broken down by reason such as queue_full.*\n\n###### How it's computed:\n*Per-second rate of the cumulative dropped-transaction counter over the dashboard's rate interval, split by reason.*\n\n###### Reading it:\n*Near zero is healthy; queue_full rejections mean the queue is at capacity and applying backpressure.*\n\n###### Healthy range:\n*Near 0 rejections per second.*\n\n###### Watch for:\n*A burst of queue_full drops, distinct from expiry, indicating the node is being flooded faster than it can drain.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`incrementTxqDropped (caller TxQ.cpp)`", + "description": "###### What this is:\n*Transactions refused entry to the queue, broken down by reason such as queue_full.*\n\n###### How it's computed:\n*Per-second rate of the cumulative dropped-transaction counter over the dashboard's rate interval, split by reason.*\n\n###### Reading it:\n*Near zero is healthy; queue_full rejections mean the queue is at capacity and applying backpressure.*\n\n###### Healthy range:\n*Near 0 rejections per second.*\n\n###### Watch for:\n*A burst of queue_full drops, distinct from expiry, indicating the node is being flooded faster than it can drain.*\n\n###### Keywords:\n- **Queue admission rejection** *(per node)* \u2014 a transaction refused entry to the queue, e.g. queue_full when the queue is at capacity.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`incrementTxqDropped (caller TxQ.cpp)`\n\n###### References:\n[Queue admission rejection](https://xrpl.org/docs/concepts/transactions/transaction-queue) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#queue-admission-rejection)", "type": "timeseries", "gridPos": { "h": 8, @@ -364,14 +410,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (reason, service_instance_id, xrpl_branch, xrpl_node_role) (rate(txq_dropped_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])), \"series\", \"$1\", \"reason\", \"(.*)\")" + "expr": "label_replace(label_join(label_replace(sum by (reason, service_instance_id, xrpl_branch, xrpl_node_role) (rate(txq_dropped_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])), \"series\", \"$1\", \"reason\", \"(.*)\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: transactions/s", "custom": { "axisLabel": "Transactions / Sec", @@ -389,6 +436,19 @@ "tags": ["transactions", "fees"], "templating": { "list": [ + { + "name": "DS_PROMETHEUS", + "type": "datasource", + "label": "Prometheus", + "query": "prometheus", + "regex": "", + "current": {}, + "hide": 0, + "refresh": 1, + "includeAll": false, + "multi": false, + "options": [] + }, { "name": "service_name", "label": "Service Name", @@ -397,7 +457,7 @@ "query": "label_values(service_name)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -417,7 +477,7 @@ "query": "label_values(deployment_environment)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -437,7 +497,7 @@ "query": "label_values(xrpl_network_type)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -517,7 +577,7 @@ "query": "label_values(target_info, service_instance_id)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -540,5 +600,5 @@ "title": "Fee Market & TxQ", "uid": "fee-market", "version": 1, - "refresh": "5s" + "refresh": "30s" } diff --git a/docker/telemetry/grafana/dashboards/job-queue.json b/docker/telemetry/grafana/dashboards/job-queue.json index 3c7d229316..f9b5376ece 100644 --- a/docker/telemetry/grafana/dashboards/job-queue.json +++ b/docker/telemetry/grafana/dashboards/job-queue.json @@ -11,7 +11,7 @@ "panels": [ { "title": "Current Job Latency (p99 Gauge)", - "description": "###### What this is:\n*At-a-glance p99 of how long jobs wait in the queue and how long they run once started.*\n\n###### How it's computed:\n*99th percentile derived from the job wait-time and run-time histograms over the last 5 minutes.*\n\n###### Reading it:\n*Lower is better; green under 100ms, yellow to 1s, red beyond 1s.*\n\n###### Healthy range:\n*Wait and exec p99 under 100ms.*\n\n###### Watch for:\n*p99 wait climbing into the red, meaning worker threads are saturated and jobs are backing up.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`recordJobStarted / recordJobFinished`", + "description": "###### What this is:\n*At-a-glance p99 of how long jobs wait in the queue and how long they run once started.*\n\n###### How it's computed:\n*99th percentile derived from the job wait-time and run-time histograms over the last 5 minutes.*\n\n###### Reading it:\n*Lower is better; green under 100ms, yellow to 1s, red beyond 1s.*\n\n###### Healthy range:\n*Wait and exec p99 under 100ms.*\n\n###### Watch for:\n*p99 wait climbing into the red, meaning worker threads are saturated and jobs are backing up.*\n\n###### Keywords:\n- **Job queue / job type** *(per node)* \u2014 xrpld's worker-thread pool; every unit of background work is enqueued under a named job type.\n- **Deferred job** *(per node)* \u2014 a job held back because its type is already at its concurrency limit; the leading indicator of queue backpressure.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`recordJobStarted / recordJobFinished`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#job-queue-job-type)", "type": "gauge", "gridPos": { "h": 8, @@ -46,7 +46,7 @@ "fieldConfig": { "defaults": { "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "µs", + "unit": "\u00b5s", "min": 0, "thresholds": { "mode": "absolute", @@ -72,7 +72,7 @@ }, { "title": "Job Throughput Rate (Per Second)", - "description": "###### What this is:\n*Rate of jobs queued, started, and finished across all job types.*\n\n###### How it's computed:\n*Per-second rate of each cumulative job counter over a 5-minute window.*\n\n###### Reading it:\n*Queued, started, and finished tracking together means the queue keeps up.*\n\n###### Healthy range:\n*Workload-dependent; the three rates should stay roughly equal.*\n\n###### Watch for:\n*Queued rate persistently above finished rate, which indicates a growing backlog.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`recordJobQueued / recordJobStarted / recordJobFinished`", + "description": "###### What this is:\n*Rate of jobs queued, started, and finished across all job types.*\n\n###### How it's computed:\n*Per-second rate of each cumulative job counter over a 5-minute window.*\n\n###### Reading it:\n*Queued, started, and finished tracking together means the queue keeps up.*\n\n###### Healthy range:\n*Workload-dependent; the three rates should stay roughly equal.*\n\n###### Watch for:\n*Queued rate persistently above finished rate, which indicates a growing backlog.*\n\n###### Keywords:\n- **Job queue / job type** *(per node)* \u2014 xrpld's worker-thread pool; every unit of background work is enqueued under a named job type.\n- **Deferred job** *(per node)* \u2014 a job held back because its type is already at its concurrency limit; the leading indicator of queue backpressure.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`recordJobQueued / recordJobStarted / recordJobFinished`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#job-queue-job-type)", "type": "timeseries", "gridPos": { "h": 8, @@ -92,19 +92,19 @@ "datasource": { "type": "prometheus" }, - "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_queued_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", job_type=~\"$job_type\", handler=~\"$handler\"}[$__rate_interval])), \"series\", \"Queued/s\", \"\", \"\")" + "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role, xrpl_work_item) (rate(job_queued_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", job_type=~\"$job_type\", handler=~\"$handler\"}[$__rate_interval])), \"series\", \"Queued/s\", \"\", \"\")" }, { "datasource": { "type": "prometheus" }, - "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_started_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", job_type=~\"$job_type\", handler=~\"$handler\"}[$__rate_interval])), \"series\", \"Started/s\", \"\", \"\")" + "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role, xrpl_work_item) (rate(job_started_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", job_type=~\"$job_type\", handler=~\"$handler\"}[$__rate_interval])), \"series\", \"Started/s\", \"\", \"\")" }, { "datasource": { "type": "prometheus" }, - "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_finished_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", job_type=~\"$job_type\", handler=~\"$handler\"}[$__rate_interval])), \"series\", \"Finished/s\", \"\", \"\")" + "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role, xrpl_work_item) (rate(job_finished_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", job_type=~\"$job_type\", handler=~\"$handler\"}[$__rate_interval])), \"series\", \"Finished/s\", \"\", \"\")" } ], "fieldConfig": { @@ -131,7 +131,7 @@ }, { "title": "Per-Job-Type Queued Rate", - "description": "###### What this is:\n*Rate of jobs entering the queue, broken down by job type (top 10).*\n\n###### How it's computed:\n*Per-second rate of the queued-job counter per job_type over a 5-minute window.*\n\n###### Reading it:\n*Identifies which job types generate the most queue activity.*\n\n###### Healthy range:\n*Workload-dependent.*\n\n###### Watch for:\n*A single job type surging unexpectedly, which can point to a flood of a particular request or peer message.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`recordJobQueued`", + "description": "###### What this is:\n*Rate of jobs entering the queue, broken down by job type (top 10).*\n\n###### How it's computed:\n*Per-second rate of the queued-job counter per job_type over a 5-minute window.*\n\n###### Reading it:\n*Identifies which job types generate the most queue activity.*\n\n###### Healthy range:\n*Workload-dependent.*\n\n###### Watch for:\n*A single job type surging unexpectedly, which can point to a flood of a particular request or peer message.*\n\n###### Keywords:\n- **Job queue / job type** *(per node)* \u2014 xrpld's worker-thread pool; every unit of background work is enqueued under a named job type.\n- **Deferred job** *(per node)* \u2014 a job held back because its type is already at its concurrency limit; the leading indicator of queue backpressure.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`recordJobQueued`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#job-queue-job-type)", "type": "timeseries", "gridPos": { "h": 8, @@ -156,7 +156,7 @@ "datasource": { "type": "prometheus" }, - "expr": "label_replace(topk(10, sum by (job_type, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_queued_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", job_type=~\"$job_type\", handler=~\"$handler\"}[$__rate_interval]))), \"series\", \"$1\", \"job_type\", \"(.*)\")" + "expr": "label_replace(topk(10, sum by (job_type, service_instance_id, xrpl_branch, xrpl_node_role, xrpl_work_item) (rate(job_queued_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", job_type=~\"$job_type\", handler=~\"$handler\"}[$__rate_interval]))), \"series\", \"$1\", \"job_type\", \"(.*)\")" } ], "fieldConfig": { @@ -183,7 +183,7 @@ }, { "title": "Per-Job-Type Finish Rate", - "description": "###### What this is:\n*Rate of jobs completing, broken down by job type (top 10).*\n\n###### How it's computed:\n*Per-second rate of the finished-job counter per job_type over a 5-minute window.*\n\n###### Reading it:\n*Compare against the queued rate per type to spot which types are falling behind.*\n\n###### Healthy range:\n*Workload-dependent; should match the per-type queued rate.*\n\n###### Watch for:\n*A type whose finish rate lags its queued rate, revealing where the backlog concentrates.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`recordJobFinished`", + "description": "###### What this is:\n*Rate of jobs completing, broken down by job type (top 10).*\n\n###### How it's computed:\n*Per-second rate of the finished-job counter per job_type over a 5-minute window.*\n\n###### Reading it:\n*Compare against the queued rate per type to spot which types are falling behind.*\n\n###### Healthy range:\n*Workload-dependent; should match the per-type queued rate.*\n\n###### Watch for:\n*A type whose finish rate lags its queued rate, revealing where the backlog concentrates.*\n\n###### Keywords:\n- **Job queue / job type** *(per node)* \u2014 xrpld's worker-thread pool; every unit of background work is enqueued under a named job type.\n- **Deferred job** *(per node)* \u2014 a job held back because its type is already at its concurrency limit; the leading indicator of queue backpressure.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`recordJobFinished`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#job-queue-job-type)", "type": "timeseries", "gridPos": { "h": 8, @@ -208,7 +208,7 @@ "datasource": { "type": "prometheus" }, - "expr": "label_replace(topk(10, sum by (job_type, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_finished_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", job_type=~\"$job_type\", handler=~\"$handler\"}[$__rate_interval]))), \"series\", \"$1\", \"job_type\", \"(.*)\")" + "expr": "label_replace(topk(10, sum by (job_type, service_instance_id, xrpl_branch, xrpl_node_role, xrpl_work_item) (rate(job_finished_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", job_type=~\"$job_type\", handler=~\"$handler\"}[$__rate_interval]))), \"series\", \"$1\", \"job_type\", \"(.*)\")" } ], "fieldConfig": { @@ -235,7 +235,7 @@ }, { "title": "Job Queue Wait Time", - "description": "###### What this is:\n*Distribution of how long jobs sit in the queue before a worker picks them up (p75 typical, p99 tail).*\n\n###### How it's computed:\n*Percentiles derived from the job wait-time histogram over a 5-minute window.*\n\n###### Reading it:\n*Lower is better; a widening gap between p75 and p99 signals occasional stalls.*\n\n###### Healthy range:\n*Sub-millisecond to low-millisecond waits on an unloaded node.*\n\n###### Watch for:\n*Rising p99 wait, an early sign of worker-thread saturation.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`recordJobStarted`", + "description": "###### What this is:\n*How long jobs sit in the queue before a worker picks them up, split by job type (p75 typical, p99 tail).*\n\n###### How it's computed:\n*Percentiles derived from the job wait-time histogram over a 5-minute window, kept per job type. Limited to the ten types with the highest wait so the legend stays readable.*\n\n###### Reading it:\n*Lower is better. The legend names the job type, so a single slow queue is identifiable rather than hidden in an all-types average. A widening p75-to-p99 gap on one type signals occasional stalls there.*\n\n###### Healthy range:\n*Sub-millisecond to low-millisecond waits on an unloaded node.*\n\n###### Watch for:\n*Rising p99 wait on a capped type -- ledgerRequest, ledgerData and makeFetchPack have small concurrency limits, so they queue first. Cross-check the deferred gauge for that type.*\n\n###### Keywords:\n- **Job queue** *(per node)* \u2014 the worker pool that runs xrpld's background work; each unit of work is a job with a type.\n- **Dequeue wait** *(per node)* \u2014 time a job sits enqueued before a worker starts it, as distinct from how long it then runs.\n- **Concurrency limit** *(per node)* \u2014 the maximum number of jobs of one type allowed to run at once; work beyond it is deferred, not rejected.\n\n###### Computation boundary:\n*Result: Per node and job type \u2014 each series is one server's own value for one job type.*\n*Recorded in code as an OTel SDK histogram, then aggregated to percentiles by the Grafana query.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`recordJobStarted`\n\n###### References:\n[Job queue](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#job-queue-job-type) \u00b7 [Concurrency limit](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#concurrency-limit)\n", "type": "timeseries", "gridPos": { "h": 8, @@ -255,24 +255,24 @@ "datasource": { "type": "prometheus" }, - "expr": "label_replace(histogram_quantile(0.75, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_queued_us_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", job_type=~\"$job_type\", handler=~\"$handler\"}[5m]))), \"series\", \"p75 Wait\", \"\", \"\")" + "expr": "label_replace(topk(10, histogram_quantile(0.75, sum by (le, job_type, service_instance_id, xrpl_branch, xrpl_node_role, xrpl_work_item) (rate(job_queued_us_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", job_type=~\"$job_type\", handler=~\"$handler\"}[5m])))), \"series\", \"p75 Wait $1\", \"job_type\", \"(.*)\")" }, { "datasource": { "type": "prometheus" }, - "expr": "label_replace(histogram_quantile(0.99, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_queued_us_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", job_type=~\"$job_type\", handler=~\"$handler\"}[5m]))), \"series\", \"p99 Wait\", \"\", \"\")" + "expr": "label_replace(topk(10, histogram_quantile(0.99, sum by (le, job_type, service_instance_id, xrpl_branch, xrpl_node_role, xrpl_work_item) (rate(job_queued_us_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", job_type=~\"$job_type\", handler=~\"$handler\"}[5m])))), \"series\", \"p99 Wait $1\", \"job_type\", \"(.*)\")" } ], "fieldConfig": { "defaults": { "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "µs", + "unit": "\u00b5s", "custom": { "drawStyle": "line", "lineWidth": 2, "fillOpacity": 5, - "axisLabel": "Duration (µs)", + "axisLabel": "Duration (\u00b5s)", "spanNulls": 1800000, "insertNulls": false, "showPoints": "auto", @@ -288,7 +288,7 @@ }, { "title": "Job Execution Time", - "description": "###### What this is:\n*Distribution of how long jobs run once started (p75 typical, p99 tail).*\n\n###### How it's computed:\n*Percentiles derived from the job run-time histogram over a 5-minute window.*\n\n###### Reading it:\n*Lower is better; stable p75 with a controlled p99 is healthy.*\n\n###### Healthy range:\n*Workload-dependent, but stable over time.*\n\n###### Watch for:\n*Growing execution times, which point to CPU pressure or expensive individual jobs.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`recordJobFinished`", + "description": "###### What this is:\n*How long jobs run once started, split by job type (p75 typical, p99 tail).*\n\n###### How it's computed:\n*Percentiles derived from the job run-time histogram over a 5-minute window, kept per job type. Limited to the ten slowest types so the legend stays readable.*\n\n###### Reading it:\n*Lower is better. The legend names the job type, so an expensive job type is identifiable rather than averaged away. Stable p75 with a controlled p99 is healthy.*\n\n###### Healthy range:\n*Workload-dependent, but stable over time.*\n\n###### Watch for:\n*Growing execution times, which point to CPU pressure or expensive individual jobs.*\n\n###### Keywords:\n- **Job queue** *(per node)* \u2014 the worker pool that runs xrpld's background work; each unit of work is a job with a type.\n- **Execution time** *(per node)* \u2014 time a job spends running after a worker picks it up, excluding its queue wait.\n\n###### Computation boundary:\n*Result: Per node and job type \u2014 each series is one server's own value for one job type.*\n*Recorded in code as an OTel SDK histogram, then aggregated to percentiles by the Grafana query.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`recordJobFinished`\n\n###### References:\n[Job queue](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#job-queue-job-type)\n", "type": "timeseries", "gridPos": { "h": 8, @@ -308,24 +308,24 @@ "datasource": { "type": "prometheus" }, - "expr": "label_replace(histogram_quantile(0.75, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_running_us_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", job_type=~\"$job_type\", handler=~\"$handler\"}[5m]))), \"series\", \"p75 Exec\", \"\", \"\")" + "expr": "label_replace(topk(10, histogram_quantile(0.75, sum by (le, job_type, service_instance_id, xrpl_branch, xrpl_node_role, xrpl_work_item) (rate(job_running_us_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", job_type=~\"$job_type\", handler=~\"$handler\"}[5m])))), \"series\", \"p75 Exec $1\", \"job_type\", \"(.*)\")" }, { "datasource": { "type": "prometheus" }, - "expr": "label_replace(histogram_quantile(0.99, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_running_us_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", job_type=~\"$job_type\", handler=~\"$handler\"}[5m]))), \"series\", \"p99 Exec\", \"\", \"\")" + "expr": "label_replace(topk(10, histogram_quantile(0.99, sum by (le, job_type, service_instance_id, xrpl_branch, xrpl_node_role, xrpl_work_item) (rate(job_running_us_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", job_type=~\"$job_type\", handler=~\"$handler\"}[5m])))), \"series\", \"p99 Exec $1\", \"job_type\", \"(.*)\")" } ], "fieldConfig": { "defaults": { "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "µs", + "unit": "\u00b5s", "custom": { "drawStyle": "line", "lineWidth": 2, "fillOpacity": 5, - "axisLabel": "Duration (µs)", + "axisLabel": "Duration (\u00b5s)", "spanNulls": 1800000, "insertNulls": false, "showPoints": "auto", @@ -341,7 +341,7 @@ }, { "title": "Per-Job-Type Execution Time (p99)", - "description": "###### What this is:\n*The 10 slowest job types ranked by p99 execution time.*\n\n###### How it's computed:\n*p99 derived from the run-time histogram per job_type over a 5-minute window, top 10 selected.*\n\n###### Reading it:\n*Highlights which job types cost the most CPU time at the tail.*\n\n###### Healthy range:\n*Workload-dependent.*\n\n###### Watch for:\n*A job type whose p99 grows over time, indicating a slow or degrading operation.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`recordJobFinished`", + "description": "###### What this is:\n*The 10 slowest job types ranked by p99 execution time.*\n\n###### How it's computed:\n*p99 derived from the run-time histogram per job_type over a 5-minute window, top 10 selected.*\n\n###### Reading it:\n*Highlights which job types cost the most CPU time at the tail.*\n\n###### Healthy range:\n*Workload-dependent.*\n\n###### Watch for:\n*A job type whose p99 grows over time, indicating a slow or degrading operation.*\n\n###### Keywords:\n- **Job queue / job type** *(per node)* \u2014 xrpld's worker-thread pool; every unit of background work is enqueued under a named job type.\n- **Deferred job** *(per node)* \u2014 a job held back because its type is already at its concurrency limit; the leading indicator of queue backpressure.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`recordJobFinished`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#job-queue-job-type)", "type": "timeseries", "gridPos": { "h": 8, @@ -366,18 +366,18 @@ "datasource": { "type": "prometheus" }, - "expr": "label_replace(topk(10, histogram_quantile(0.99, sum by (le, job_type, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_running_us_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", job_type=~\"$job_type\", handler=~\"$handler\"}[5m])))), \"series\", \"$1\", \"job_type\", \"(.*)\")" + "expr": "label_replace(topk(10, histogram_quantile(0.99, sum by (le, job_type, service_instance_id, xrpl_branch, xrpl_node_role, xrpl_work_item) (rate(job_running_us_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", job_type=~\"$job_type\", handler=~\"$handler\"}[5m])))), \"series\", \"$1\", \"job_type\", \"(.*)\")" } ], "fieldConfig": { "defaults": { "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "µs", + "unit": "\u00b5s", "custom": { "drawStyle": "line", "lineWidth": 1, "fillOpacity": 5, - "axisLabel": "Duration (µs)", + "axisLabel": "Duration (\u00b5s)", "spanNulls": 1800000, "insertNulls": false, "showPoints": "auto", @@ -393,7 +393,7 @@ }, { "title": "Transaction Overflow Rate", - "description": "###### What this is:\n*Rate at which transaction jobs are shed when the queue's transaction limit is exceeded.*\n\n###### How it's computed:\n*Per-second rate of the overflow counter over the dashboard's rate interval, scaled to per minute. The counter is observed from the overlay's cumulative overflow tally.*\n\n###### Reading it:\n*Near zero is healthy; a rising rate means the job queue is shedding transaction work under load.*\n\n###### Healthy range:\n*0 overflows per minute.*\n\n###### Watch for:\n*Any sustained non-zero rate — the node is dropping transaction jobs because the queue is saturated.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerParityCounters (observed from Overlay::getJqTransOverflow)`", + "description": "###### What this is:\n*Rate at which transaction jobs are shed when the queue's transaction limit is exceeded.*\n\n###### How it's computed:\n*Per-second rate of the overflow counter over the dashboard's rate interval, scaled to per minute. The counter is observed from the overlay's cumulative overflow tally.*\n\n###### Reading it:\n*Near zero is healthy; a rising rate means the job queue is shedding transaction work under load.*\n\n###### Healthy range:\n*0 overflows per minute.*\n\n###### Watch for:\n*Any sustained non-zero rate \u2014 the node is dropping transaction jobs because the queue is saturated.*\n\n###### Keywords:\n- **Job queue / job type** *(per node)* \u2014 xrpld's worker-thread pool; every unit of background work is enqueued under a named job type.\n- **Transaction queue (TxQ)** *(per node)* \u2014 holds transactions that cannot enter the open ledger yet, ordered by fee level.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerParityCounters (observed from Overlay::getJqTransOverflow)`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#job-queue-job-type)", "type": "timeseries", "gridPos": { "h": 8, diff --git a/docker/telemetry/grafana/dashboards/ledger-data-sync.json b/docker/telemetry/grafana/dashboards/ledger-data-sync.json index 3bd7745985..640b5b464c 100644 --- a/docker/telemetry/grafana/dashboards/ledger-data-sync.json +++ b/docker/telemetry/grafana/dashboards/ledger-data-sync.json @@ -11,7 +11,7 @@ "panels": [ { "title": "Ledger Data \u2014 Ledger", - "description": "###### What this is:\n*Inbound bytes for ledger-data message categories, split into aggregate get/share plus the transaction-set, transaction-node, and account-state-node sub-types the node receives from peers.*\n\n###### How it's computed:\n*Per-category inbound byte rate per node.*\n\n###### Reading it:\n*Normally low and flat once synced. Account-state-node traffic dominates during state sync; transaction-set-candidate traffic dominates during consensus catch-up.*\n\n###### Healthy range:\n*workload-dependent; low and steady on a synced node.*\n\n###### Watch for:\n*Sustained high account-state or tx-node inbound bytes on a node that should be caught up (repeated re-sync, missing history), or a single peer driving all traffic.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`", + "description": "###### What this is:\n*Inbound bytes for ledger-data message categories, split into aggregate get/share plus the transaction-set, transaction-node, and account-state-node sub-types the node receives from peers.*\n\n###### How it's computed:\n*Per-category inbound byte rate per node.*\n\n###### Reading it:\n*Normally low and flat once synced. Account-state-node traffic dominates during state sync; transaction-set-candidate traffic dominates during consensus catch-up.*\n\n###### Healthy range:\n*workload-dependent; low and steady on a synced node.*\n\n###### Watch for:\n*Sustained high account-state or tx-node inbound bytes on a node that should be caught up (repeated re-sync, missing history), or a single peer driving all traffic.*\n\n###### Keywords:\n- **Ledger data** *(network event)* \u2014 the bulk transfer of ledger contents between peers during sync.\n- **Overlay** *(network-wide)* \u2014 the peer-to-peer mesh xrpld nodes form to gossip transactions, proposals and validations.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#ledger-data)", "type": "timeseries", "gridPos": { "h": 8, @@ -58,7 +58,7 @@ }, { "title": "Ledger Data \u2014 Transaction", - "description": "###### What this is:\n*Inbound bytes for ledger-data message categories, split into aggregate get/share plus the transaction-set, transaction-node, and account-state-node sub-types the node receives from peers.*\n\n###### How it's computed:\n*Per-category inbound byte rate per node.*\n\n###### Reading it:\n*Normally low and flat once synced. Account-state-node traffic dominates during state sync; transaction-set-candidate traffic dominates during consensus catch-up.*\n\n###### Healthy range:\n*workload-dependent; low and steady on a synced node.*\n\n###### Watch for:\n*Sustained high account-state or tx-node inbound bytes on a node that should be caught up (repeated re-sync, missing history), or a single peer driving all traffic.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`", + "description": "###### What this is:\n*Inbound bytes for ledger-data message categories, split into aggregate get/share plus the transaction-set, transaction-node, and account-state-node sub-types the node receives from peers.*\n\n###### How it's computed:\n*Per-category inbound byte rate per node.*\n\n###### Reading it:\n*Normally low and flat once synced. Account-state-node traffic dominates during state sync; transaction-set-candidate traffic dominates during consensus catch-up.*\n\n###### Healthy range:\n*workload-dependent; low and steady on a synced node.*\n\n###### Watch for:\n*Sustained high account-state or tx-node inbound bytes on a node that should be caught up (repeated re-sync, missing history), or a single peer driving all traffic.*\n\n###### Keywords:\n- **Ledger data** *(network event)* \u2014 the bulk transfer of ledger contents between peers during sync.\n- **Overlay** *(network-wide)* \u2014 the peer-to-peer mesh xrpld nodes form to gossip transactions, proposals and validations.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#ledger-data)", "type": "timeseries", "gridPos": { "h": 8, @@ -117,7 +117,7 @@ }, { "title": "Ledger Data \u2014 Account State", - "description": "###### What this is:\n*Inbound bytes for ledger-data message categories, split into aggregate get/share plus the transaction-set, transaction-node, and account-state-node sub-types the node receives from peers.*\n\n###### How it's computed:\n*Per-category inbound byte rate per node.*\n\n###### Reading it:\n*Normally low and flat once synced. Account-state-node traffic dominates during state sync; transaction-set-candidate traffic dominates during consensus catch-up.*\n\n###### Healthy range:\n*workload-dependent; low and steady on a synced node.*\n\n###### Watch for:\n*Sustained high account-state or tx-node inbound bytes on a node that should be caught up (repeated re-sync, missing history), or a single peer driving all traffic.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`", + "description": "###### What this is:\n*Inbound bytes for ledger-data message categories, split into aggregate get/share plus the transaction-set, transaction-node, and account-state-node sub-types the node receives from peers.*\n\n###### How it's computed:\n*Per-category inbound byte rate per node.*\n\n###### Reading it:\n*Normally low and flat once synced. Account-state-node traffic dominates during state sync; transaction-set-candidate traffic dominates during consensus catch-up.*\n\n###### Healthy range:\n*workload-dependent; low and steady on a synced node.*\n\n###### Watch for:\n*Sustained high account-state or tx-node inbound bytes on a node that should be caught up (repeated re-sync, missing history), or a single peer driving all traffic.*\n\n###### Keywords:\n- **Ledger data** *(network event)* \u2014 the bulk transfer of ledger contents between peers during sync.\n- **Overlay** *(network-wide)* \u2014 the peer-to-peer mesh xrpld nodes form to gossip transactions, proposals and validations.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#ledger-data)", "type": "timeseries", "gridPos": { "h": 8, @@ -164,7 +164,7 @@ }, { "title": "Ledger Traffic \u2014 Ledger", - "description": "###### What this is:\n*Inbound bytes for the older ledger share/get message categories and their tx-set, tx-node, and account-state sub-types.*\n\n###### How it's computed:\n*Per-category inbound byte rate per node.*\n\n###### Reading it:\n*Usually small; these legacy categories carry ledger-fetch traffic for peers using the older protocol.*\n\n###### Healthy range:\n*workload-dependent; low on a synced node.*\n\n###### Watch for:\n*Large sustained volumes indicating heavy fetch load or a peer repeatedly requesting the same data.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`", + "description": "###### What this is:\n*Inbound bytes for the older ledger share/get message categories and their tx-set, tx-node, and account-state sub-types.*\n\n###### How it's computed:\n*Per-category inbound byte rate per node.*\n\n###### Reading it:\n*Usually small; these legacy categories carry ledger-fetch traffic for peers using the older protocol.*\n\n###### Healthy range:\n*workload-dependent; low on a synced node.*\n\n###### Watch for:\n*Large sustained volumes indicating heavy fetch load or a peer repeatedly requesting the same data.*\n\n###### Keywords:\n- **Overlay** *(network-wide)* \u2014 the peer-to-peer mesh xrpld nodes form to gossip transactions, proposals and validations.\n- **Peer** *(per node)* \u2014 another server this node holds a protocol connection to.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#overlay)", "type": "timeseries", "gridPos": { "h": 8, @@ -211,7 +211,7 @@ }, { "title": "Ledger Traffic \u2014 Transaction", - "description": "###### What this is:\n*Inbound bytes for the older ledger share/get message categories and their tx-set, tx-node, and account-state sub-types.*\n\n###### How it's computed:\n*Per-category inbound byte rate per node.*\n\n###### Reading it:\n*Usually small; these legacy categories carry ledger-fetch traffic for peers using the older protocol.*\n\n###### Healthy range:\n*workload-dependent; low on a synced node.*\n\n###### Watch for:\n*Large sustained volumes indicating heavy fetch load or a peer repeatedly requesting the same data.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`", + "description": "###### What this is:\n*Inbound bytes for the older ledger share/get message categories and their tx-set, tx-node, and account-state sub-types.*\n\n###### How it's computed:\n*Per-category inbound byte rate per node.*\n\n###### Reading it:\n*Usually small; these legacy categories carry ledger-fetch traffic for peers using the older protocol.*\n\n###### Healthy range:\n*workload-dependent; low on a synced node.*\n\n###### Watch for:\n*Large sustained volumes indicating heavy fetch load or a peer repeatedly requesting the same data.*\n\n###### Keywords:\n- **Overlay** *(network-wide)* \u2014 the peer-to-peer mesh xrpld nodes form to gossip transactions, proposals and validations.\n- **Peer** *(per node)* \u2014 another server this node holds a protocol connection to.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#overlay)", "type": "timeseries", "gridPos": { "h": 8, @@ -270,7 +270,7 @@ }, { "title": "Ledger Traffic \u2014 Account State", - "description": "###### What this is:\n*Inbound bytes for the older ledger share/get message categories and their tx-set, tx-node, and account-state sub-types.*\n\n###### How it's computed:\n*Per-category inbound byte rate per node.*\n\n###### Reading it:\n*Usually small; these legacy categories carry ledger-fetch traffic for peers using the older protocol.*\n\n###### Healthy range:\n*workload-dependent; low on a synced node.*\n\n###### Watch for:\n*Large sustained volumes indicating heavy fetch load or a peer repeatedly requesting the same data.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`", + "description": "###### What this is:\n*Inbound bytes for the older ledger share/get message categories and their tx-set, tx-node, and account-state sub-types.*\n\n###### How it's computed:\n*Per-category inbound byte rate per node.*\n\n###### Reading it:\n*Usually small; these legacy categories carry ledger-fetch traffic for peers using the older protocol.*\n\n###### Healthy range:\n*workload-dependent; low on a synced node.*\n\n###### Watch for:\n*Large sustained volumes indicating heavy fetch load or a peer repeatedly requesting the same data.*\n\n###### Keywords:\n- **Overlay** *(network-wide)* \u2014 the peer-to-peer mesh xrpld nodes form to gossip transactions, proposals and validations.\n- **Peer** *(per node)* \u2014 another server this node holds a protocol connection to.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#overlay)", "type": "timeseries", "gridPos": { "h": 8, @@ -317,7 +317,7 @@ }, { "title": "GetObject \u2014 Ledger", - "description": "###### What this is:\n*Inbound bytes for object-fetch traffic broken down by object type: ledger headers, individual transactions, transaction-tree nodes, and state-tree nodes.*\n\n###### How it's computed:\n*Per-type inbound byte rate per node.*\n\n###### Reading it:\n*Small during steady state; grows when the node fetches missing tree nodes.*\n\n###### Healthy range:\n*workload-dependent; low when synced.*\n\n###### Watch for:\n*A large share on state/tx nodes for long periods (persistent gap-filling), meaning the node keeps catching up.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`", + "description": "###### What this is:\n*Inbound bytes for object-fetch traffic broken down by object type: ledger headers, individual transactions, transaction-tree nodes, and state-tree nodes.*\n\n###### How it's computed:\n*Per-type inbound byte rate per node.*\n\n###### Reading it:\n*Small during steady state; grows when the node fetches missing tree nodes.*\n\n###### Healthy range:\n*workload-dependent; low when synced.*\n\n###### Watch for:\n*A large share on state/tx nodes for long periods (persistent gap-filling), meaning the node keeps catching up.*\n\n###### Keywords:\n- **GetObject** *(network event)* \u2014 a peer request for specific ledger objects by hash, served from the NodeStore.\n- **NodeStore** *(per node)* \u2014 the key-value object store holding ledger data (tree nodes), backed by NuDB.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#nodestore)", "type": "timeseries", "gridPos": { "h": 8, @@ -364,7 +364,7 @@ }, { "title": "GetObject \u2014 Transaction", - "description": "###### What this is:\n*Inbound bytes for object-fetch traffic broken down by object type: ledger headers, individual transactions, transaction-tree nodes, and state-tree nodes.*\n\n###### How it's computed:\n*Per-type inbound byte rate per node.*\n\n###### Reading it:\n*Small during steady state; grows when the node fetches missing tree nodes.*\n\n###### Healthy range:\n*workload-dependent; low when synced.*\n\n###### Watch for:\n*A large share on state/tx nodes for long periods (persistent gap-filling), meaning the node keeps catching up.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`", + "description": "###### What this is:\n*Inbound bytes for object-fetch traffic broken down by object type: ledger headers, individual transactions, transaction-tree nodes, and state-tree nodes.*\n\n###### How it's computed:\n*Per-type inbound byte rate per node.*\n\n###### Reading it:\n*Small during steady state; grows when the node fetches missing tree nodes.*\n\n###### Healthy range:\n*workload-dependent; low when synced.*\n\n###### Watch for:\n*A large share on state/tx nodes for long periods (persistent gap-filling), meaning the node keeps catching up.*\n\n###### Keywords:\n- **GetObject** *(network event)* \u2014 a peer request for specific ledger objects by hash, served from the NodeStore.\n- **NodeStore** *(per node)* \u2014 the key-value object store holding ledger data (tree nodes), backed by NuDB.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#nodestore)", "type": "timeseries", "gridPos": { "h": 8, @@ -423,7 +423,7 @@ }, { "title": "GetObject \u2014 Account State", - "description": "###### What this is:\n*Inbound bytes for object-fetch traffic broken down by object type: ledger headers, individual transactions, transaction-tree nodes, and state-tree nodes.*\n\n###### How it's computed:\n*Per-type inbound byte rate per node.*\n\n###### Reading it:\n*Small during steady state; grows when the node fetches missing tree nodes.*\n\n###### Healthy range:\n*workload-dependent; low when synced.*\n\n###### Watch for:\n*A large share on state/tx nodes for long periods (persistent gap-filling), meaning the node keeps catching up.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`", + "description": "###### What this is:\n*Inbound bytes for object-fetch traffic broken down by object type: ledger headers, individual transactions, transaction-tree nodes, and state-tree nodes.*\n\n###### How it's computed:\n*Per-type inbound byte rate per node.*\n\n###### Reading it:\n*Small during steady state; grows when the node fetches missing tree nodes.*\n\n###### Healthy range:\n*workload-dependent; low when synced.*\n\n###### Watch for:\n*A large share on state/tx nodes for long periods (persistent gap-filling), meaning the node keeps catching up.*\n\n###### Keywords:\n- **GetObject** *(network event)* \u2014 a peer request for specific ledger objects by hash, served from the NodeStore.\n- **NodeStore** *(per node)* \u2014 the key-value object store holding ledger data (tree nodes), backed by NuDB.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#nodestore)", "type": "timeseries", "gridPos": { "h": 8, @@ -470,7 +470,7 @@ }, { "title": "GetObject Messages \u2014 Ledger", - "description": "###### What this is:\n*Count of individual object-fetch request/response messages per object type.*\n\n###### How it's computed:\n*Per-type inbound message rate per node.*\n\n###### Reading it:\n*Many messages with few bytes means small piecemeal fetches; few messages with many bytes means large batch transfers.*\n\n###### Healthy range:\n*workload-dependent.*\n\n###### Watch for:\n*High message counts with tiny payloads sustained over time (inefficient per-node fetching).*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`", + "description": "###### What this is:\n*Count of individual object-fetch request/response messages per object type.*\n\n###### How it's computed:\n*Per-type inbound message rate per node.*\n\n###### Reading it:\n*Many messages with few bytes means small piecemeal fetches; few messages with many bytes means large batch transfers.*\n\n###### Healthy range:\n*workload-dependent.*\n\n###### Watch for:\n*High message counts with tiny payloads sustained over time (inefficient per-node fetching).*\n\n###### Keywords:\n- **GetObject** *(network event)* \u2014 a peer request for specific ledger objects by hash, served from the NodeStore.\n- **NodeStore** *(per node)* \u2014 the key-value object store holding ledger data (tree nodes), backed by NuDB.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#nodestore)", "type": "timeseries", "gridPos": { "h": 8, @@ -511,7 +511,7 @@ }, { "title": "GetObject Messages \u2014 Transaction", - "description": "###### What this is:\n*Count of individual object-fetch request/response messages per object type.*\n\n###### How it's computed:\n*Per-type inbound message rate per node.*\n\n###### Reading it:\n*Many messages with few bytes means small piecemeal fetches; few messages with many bytes means large batch transfers.*\n\n###### Healthy range:\n*workload-dependent.*\n\n###### Watch for:\n*High message counts with tiny payloads sustained over time (inefficient per-node fetching).*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`", + "description": "###### What this is:\n*Count of individual object-fetch request/response messages per object type.*\n\n###### How it's computed:\n*Per-type inbound message rate per node.*\n\n###### Reading it:\n*Many messages with few bytes means small piecemeal fetches; few messages with many bytes means large batch transfers.*\n\n###### Healthy range:\n*workload-dependent.*\n\n###### Watch for:\n*High message counts with tiny payloads sustained over time (inefficient per-node fetching).*\n\n###### Keywords:\n- **GetObject** *(network event)* \u2014 a peer request for specific ledger objects by hash, served from the NodeStore.\n- **NodeStore** *(per node)* \u2014 the key-value object store holding ledger data (tree nodes), backed by NuDB.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#nodestore)", "type": "timeseries", "gridPos": { "h": 8, @@ -558,7 +558,7 @@ }, { "title": "GetObject Messages \u2014 Account State", - "description": "###### What this is:\n*Count of individual object-fetch request/response messages per object type.*\n\n###### How it's computed:\n*Per-type inbound message rate per node.*\n\n###### Reading it:\n*Many messages with few bytes means small piecemeal fetches; few messages with many bytes means large batch transfers.*\n\n###### Healthy range:\n*workload-dependent.*\n\n###### Watch for:\n*High message counts with tiny payloads sustained over time (inefficient per-node fetching).*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`", + "description": "###### What this is:\n*Count of individual object-fetch request/response messages per object type.*\n\n###### How it's computed:\n*Per-type inbound message rate per node.*\n\n###### Reading it:\n*Many messages with few bytes means small piecemeal fetches; few messages with many bytes means large batch transfers.*\n\n###### Healthy range:\n*workload-dependent.*\n\n###### Watch for:\n*High message counts with tiny payloads sustained over time (inefficient per-node fetching).*\n\n###### Keywords:\n- **GetObject** *(network event)* \u2014 a peer request for specific ledger objects by hash, served from the NodeStore.\n- **NodeStore** *(per node)* \u2014 the key-value object store holding ledger data (tree nodes), backed by NuDB.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#nodestore)", "type": "timeseries", "gridPos": { "h": 8, @@ -599,7 +599,7 @@ }, { "title": "GetObject Messages \u2014 Specials", - "description": "###### What this is:\n*Count of individual object-fetch request/response messages per object type.*\n\n###### How it's computed:\n*Per-type inbound message rate per node.*\n\n###### Reading it:\n*Many messages with few bytes means small piecemeal fetches; few messages with many bytes means large batch transfers.*\n\n###### Healthy range:\n*workload-dependent.*\n\n###### Watch for:\n*High message counts with tiny payloads sustained over time (inefficient per-node fetching).*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`", + "description": "###### What this is:\n*Count of individual object-fetch request/response messages per object type.*\n\n###### How it's computed:\n*Per-type inbound message rate per node.*\n\n###### Reading it:\n*Many messages with few bytes means small piecemeal fetches; few messages with many bytes means large batch transfers.*\n\n###### Healthy range:\n*workload-dependent.*\n\n###### Watch for:\n*High message counts with tiny payloads sustained over time (inefficient per-node fetching).*\n\n###### Keywords:\n- **GetObject** *(network event)* \u2014 a peer request for specific ledger objects by hash, served from the NodeStore.\n- **NodeStore** *(per node)* \u2014 the key-value object store holding ledger data (tree nodes), backed by NuDB.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#nodestore)", "type": "timeseries", "gridPos": { "h": 8, @@ -652,7 +652,7 @@ }, { "title": "GetObject \u2014 Specials", - "description": "###### What this is:\n*Aggregate object-fetch inbound bytes plus special buckets: content-addressed storage fetches, bulk fetch-pack downloads used during catch-up, and bulk transaction fetches.*\n\n###### How it's computed:\n*Per-category inbound byte rate per node.*\n\n###### Reading it:\n*Fetch-pack rises sharply while catching up a range of ledgers; near zero when fully synced.*\n\n###### Healthy range:\n*workload-dependent; low when synced.*\n\n###### Watch for:\n*Continuous fetch-pack traffic (node never fully catches up) or unexpectedly high content-store volume.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`", + "description": "###### What this is:\n*Aggregate object-fetch inbound bytes plus special buckets: content-addressed storage fetches, bulk fetch-pack downloads used during catch-up, and bulk transaction fetches.*\n\n###### How it's computed:\n*Per-category inbound byte rate per node.*\n\n###### Reading it:\n*Fetch-pack rises sharply while catching up a range of ledgers; near zero when fully synced.*\n\n###### Healthy range:\n*workload-dependent; low when synced.*\n\n###### Watch for:\n*Continuous fetch-pack traffic (node never fully catches up) or unexpectedly high content-store volume.*\n\n###### Keywords:\n- **GetObject** *(network event)* \u2014 a peer request for specific ledger objects by hash, served from the NodeStore.\n- **NodeStore** *(per node)* \u2014 the key-value object store holding ledger data (tree nodes), backed by NuDB.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#nodestore)", "type": "timeseries", "gridPos": { "h": 8, @@ -729,7 +729,7 @@ }, { "title": "Overlay Traffic Heatmap (All Categories, Bytes In)", - "description": "###### What this is:\n*All overlay traffic categories ranked by inbound bytes, giving an at-a-glance view of which message types consume the most receive bandwidth.*\n\n###### How it's computed:\n*Top categories by latest inbound byte value across all traffic categories. Each bar is labelled with its traffic category followed by the node identity; the shared `_bytes_in` suffix is dropped from the category name because the panel already reports inbound bytes.*\n\n###### Reading it:\n*The longest bars are the biggest bandwidth consumers; on a synced node transactions, proposals, and validations usually lead.*\n\n###### Healthy range:\n*workload-dependent.*\n\n###### Watch for:\n*A single ledger-data or fetch category dominating (ongoing sync) or an unexpected category topping the list.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`", + "description": "###### What this is:\n*All overlay traffic categories ranked by inbound bytes, giving an at-a-glance view of which message types consume the most receive bandwidth.*\n\n###### How it's computed:\n*Top categories by latest inbound byte value across all traffic categories. Each bar is labelled with its traffic category followed by the node identity; the shared `_bytes_in` suffix is dropped from the category name because the panel already reports inbound bytes.*\n\n###### Reading it:\n*The longest bars are the biggest bandwidth consumers; on a synced node transactions, proposals, and validations usually lead.*\n\n###### Healthy range:\n*workload-dependent.*\n\n###### Watch for:\n*A single ledger-data or fetch category dominating (ongoing sync) or an unexpected category topping the list.*\n\n###### Keywords:\n- **Overlay** *(network-wide)* \u2014 the peer-to-peer mesh xrpld nodes form to gossip transactions, proposals and validations.\n- **Peer** *(per node)* \u2014 another server this node holds a protocol connection to.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#overlay)", "type": "bargauge", "gridPos": { "h": 18, @@ -800,7 +800,7 @@ }, { "title": "Sync State", - "description": "###### What this is:\n*Current server operating state as a numeric code: 0 disconnected, 1 connected, 2 syncing, 3 tracking, 4 full, 5 validating, 6 proposing. A healthy validator sits at 6, a healthy non-validating node at 4.*\n\n###### How it's computed:\n*state_tracking{metric=\"state_value\"} (gauge). Companion time_in_current_state_seconds shows how long it has been stuck there.*\n\n###### Reading it:\n*Flat at 4-6 = full/healthy. Dropping to 1-2 and staying = the node fell out of sync and is re-acquiring (the primary red flag this row explains).*\n\n###### Healthy range:\n*4-6 steady, briefly 2-3 right after restart.*\n\n###### Watch for:\n*A node stuck below 4 for more than a few minutes, or oscillating - read the lower panels for the cause.*\n\n###### Source:\n[app/misc/NetworkOPs.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/NetworkOPs.cpp)\n\n###### Function:\n`NetworkOPsImp::setMode`", + "description": "###### What this is:\n*Current server operating state as a numeric code: 0 disconnected, 1 connected, 2 syncing, 3 tracking, 4 full, 5 validating, 6 proposing. A healthy validator sits at 6, a healthy non-validating node at 4.*\n\n###### How it's computed:\n*state_tracking{metric=\"state_value\"} (gauge). Companion time_in_current_state_seconds shows how long it has been stuck there.*\n\n###### Reading it:\n*Flat at 4-6 = full/healthy. Dropping to 1-2 and staying = the node fell out of sync and is re-acquiring (the primary red flag this row explains).*\n\n###### Healthy range:\n*4-6 steady, briefly 2-3 right after restart.*\n\n###### Watch for:\n*A node stuck below 4 for more than a few minutes, or oscillating - read the lower panels for the cause.*\n\n###### Keywords:\n- **Operating mode / server state** *(per node)* \u2014 the node's sync level: Disconnected, Connected, Syncing, Tracking, Full.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[app/misc/NetworkOPs.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/NetworkOPs.cpp)\n\n###### Function:\n`NetworkOPsImp::setMode`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#operating-mode-server-state)", "type": "timeseries", "gridPos": { "h": 8, @@ -922,7 +922,7 @@ }, { "title": "Validated Ledger Age", - "description": "###### What this is:\n*Seconds since this node last had a freshly validated ledger. The single clearest 'am I keeping up with the network' signal.*\n\n###### How it's computed:\n*ledgermaster_validated_ledger_age gauge (seconds), per node.*\n\n###### Reading it:\n*Should hover at the network close interval (~3-5s). A rising sawtooth or a high plateau means the node is falling behind or not validating.*\n\n###### Healthy range:\n*<= ~6s on a synced node.*\n\n###### Watch for:\n*Sustained climb above ~15s, or a monotonic ramp = the node is not keeping up; correlate with job-queue wait and NuDB read latency below.*\n\n###### Source:\n[app/ledger/LedgerMaster.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/detail/LedgerMaster.cpp)\n\n###### Function:\n`LedgerMaster::getValidatedLedgerAge`", + "description": "###### What this is:\n*Seconds since this node last had a freshly validated ledger. The single clearest 'am I keeping up with the network' signal.*\n\n###### How it's computed:\n*ledgermaster_validated_ledger_age gauge (seconds), per node.*\n\n###### Reading it:\n*Should hover at the network close interval (~3-5s). A rising sawtooth or a high plateau means the node is falling behind or not validating.*\n\n###### Healthy range:\n*<= ~6s on a synced node.*\n\n###### Watch for:\n*Sustained climb above ~15s, or a monotonic ramp = the node is not keeping up; correlate with job-queue wait and NuDB read latency below.*\n\n###### Keywords:\n- **Validated ledger age** *(per node)* \u2014 seconds since the last freshly validated ledger.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[app/ledger/LedgerMaster.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/detail/LedgerMaster.cpp)\n\n###### Function:\n`LedgerMaster::getValidatedLedgerAge`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#validated-ledger-age)", "type": "timeseries", "gridPos": { "h": 8, @@ -971,7 +971,7 @@ }, { "title": "Time Spent Per State", - "description": "###### What this is:\n*Operating mode as a colour-coded timeline. Each band's width is the time spent in that state, so short-lived states show as thin slivers instead of vanishing.*\n\n###### How it's computed:\n*server_info{metric=\"server_state\"} (gauge), the raw OperatingMode 0-4. Uses server_state rather than state_value because state_value folds 5 and 6 onto FULL, which would split one Full band into three colours.*\n\n###### Reading it:\n*One green band across the window = healthy. Red/orange/yellow bands show when and for how long the node was degraded.*\n\n###### Healthy range:\n*Continuously green (Full), brief orange/yellow/blue only after a restart.*\n\n###### Watch for:\n*Repeated thin bands = the node is oscillating. This is sampled every 10s, so a state shorter than one sample can still be missed.*\n\n###### Source:\n[app/misc/NetworkOPs.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/NetworkOPs.cpp)\n\n###### Function:\n`NetworkOPsImp::setMode`", + "description": "###### What this is:\n*Operating mode as a colour-coded timeline. Each band's width is the time spent in that state, so short-lived states show as thin slivers instead of vanishing.*\n\n###### How it's computed:\n*server_info{metric=\"server_state\"} (gauge), the raw OperatingMode 0-4. Uses server_state rather than state_value because state_value folds 5 and 6 onto FULL, which would split one Full band into three colours.*\n\n###### Reading it:\n*One green band across the window = healthy. Red/orange/yellow bands show when and for how long the node was degraded.*\n\n###### Healthy range:\n*Continuously green (Full), brief orange/yellow/blue only after a restart.*\n\n###### Watch for:\n*Repeated thin bands = the node is oscillating. This is sampled every 10s, so a state shorter than one sample can still be missed.*\n\n###### Keywords:\n- **Operating mode / server state** *(per node)* \u2014 the node's sync level: Disconnected, Connected, Syncing, Tracking, Full.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[app/misc/NetworkOPs.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/NetworkOPs.cpp)\n\n###### Function:\n`NetworkOPsImp::setMode`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#operating-mode-server-state)", "type": "state-timeline", "gridPos": { "h": 6, @@ -1084,7 +1084,7 @@ }, { "title": "Ledger Close Rate", - "description": "###### What this is:\n*Rate at which the node closes ledgers, versus the ~0.25/s network cadence. A throughput deficit means it is not advancing in step with the network.*\n\n###### How it's computed:\n*rate(ledgers_closed_total[$__rate_interval]) per node.*\n\n###### Reading it:\n*Should track ~0.22-0.25 ledgers/s (one every ~4s). Near-zero while behind = stalled; a burst above network rate = catching up.*\n\n###### Healthy range:\n*~0.25/s steady on a synced node.*\n\n###### Watch for:\n*Near-zero close rate while Validated Ledger Age climbs = hard stall (e.g. genesis-flapping or disk-bound acquisition).*\n\n###### Source:\n[app/ledger/LedgerMaster.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/detail/LedgerMaster.cpp)\n\n###### Function:\n`LedgerMaster::closeLedger`", + "description": "###### What this is:\n*Rate at which the node closes ledgers, versus the ~0.25/s network cadence. A throughput deficit means it is not advancing in step with the network.*\n\n###### How it's computed:\n*rate(ledgers_closed_total[$__rate_interval]) per node.*\n\n###### Reading it:\n*Should track ~0.22-0.25 ledgers/s (one every ~4s). Near-zero while behind = stalled; a burst above network rate = catching up.*\n\n###### Healthy range:\n*~0.25/s steady on a synced node.*\n\n###### Watch for:\n*Near-zero close rate while Validated Ledger Age climbs = hard stall (e.g. genesis-flapping or disk-bound acquisition).*\n\n###### Keywords:\n- **Ledger close** *(network event)* \u2014 the current open ledger is closed and a new closed ledger is built from the agreed transaction set.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[app/ledger/LedgerMaster.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/detail/LedgerMaster.cpp)\n\n###### Function:\n`LedgerMaster::closeLedger`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#ledger-close)", "type": "timeseries", "gridPos": { "h": 8, @@ -1150,7 +1150,7 @@ }, { "title": "Job Queue Wait p95 By Type", - "description": "###### What this is:\n*95th-percentile time a job waits in the queue before a worker thread picks it up, for the sync-critical job types. This is the metric form of the 'ProcessLData wait: NNNNms' warnings in the debug log.*\n\n###### How it's computed:\n*histogram_quantile(0.95, rate(jobq__q_milliseconds_bucket[$__rate_interval])) for ledgerdata, acceptledger, fetchtxndata, transaction, advanceledger, ledgerrequest.*\n\n###### Reading it:\n*Queue wait should be single-digit to low-tens of ms. High ledgerdata/fetchtxndata wait = the node cannot process inbound ledger data fast enough.*\n\n###### Healthy range:\n*< ~50ms p95 per type on a healthy node.*\n\n###### Watch for:\n*ledgerdata or fetchtxndata q-wait spiking to seconds = worker threads are blocked (usually on NuDB reads - see the cause tier).*\n\n###### Source:\n[core/JobQueue.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/core/detail/JobQueue.cpp)\n\n###### Function:\n`JobQueue::getJson (per-type queue timing)`", + "description": "###### What this is:\n*95th-percentile time a job waits in the queue before a worker thread picks it up, for the sync-critical job types. This is the metric form of the 'ProcessLData wait: NNNNms' warnings in the debug log.*\n\n###### How it's computed:\n*histogram_quantile(0.95, rate(jobq__q_milliseconds_bucket[$__rate_interval])) for ledgerdata, acceptledger, fetchtxndata, transaction, advanceledger, ledgerrequest.*\n\n###### Reading it:\n*Queue wait should be single-digit to low-tens of ms. High ledgerdata/fetchtxndata wait = the node cannot process inbound ledger data fast enough.*\n\n###### Healthy range:\n*< ~50ms p95 per type on a healthy node.*\n\n###### Watch for:\n*ledgerdata or fetchtxndata q-wait spiking to seconds = worker threads are blocked (usually on NuDB reads - see the cause tier).*\n\n###### Keywords:\n- **Job queue / job type** *(per node)* \u2014 xrpld's worker-thread pool; every unit of background work is enqueued under a named job type.\n- **Deferred job** *(per node)* \u2014 a job held back because its type is already at its concurrency limit; the leading indicator of queue backpressure.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[core/JobQueue.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/core/detail/JobQueue.cpp)\n\n###### Function:\n`JobQueue::getJson (per-type queue timing)`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#job-queue-job-type)", "type": "timeseries", "gridPos": { "h": 8, @@ -1227,7 +1227,7 @@ }, { "title": "NuDB Read Latency", - "description": "###### What this is:\n*Average nodestore (NuDB) read latency: how long each on-disk object read takes. The direct disk-layer cost that backs up the job queue.*\n\n###### How it's computed:\n*rate(nodestore_state{metric=\"node_reads_duration_us\"}[$__rate_interval]) / rate(nodestore_state{metric=\"node_reads_total\"}[$__rate_interval]), in us.*\n\n###### Reading it:\n*Low single-digit us when the OS page cache is warm; tens-to-hundreds of us when reads hit the disk. Rises sharply during cold-cache catch-up.*\n\n###### Healthy range:\n*< ~10us/read warm; higher is expected briefly after a wipe/restart.*\n\n###### Watch for:\n*Sustained high us/read is the disk-bound signal on its own; the found ratio below stays near 100% even then, so do not wait for it to drop. Check EBS IOPS / io scheduler latency.*\n\n###### Source:\n[nodestore/backend/NuDBFactory.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/nodestore/backend/NuDBFactory.cpp)\n\n###### Function:\n`NuDB backend read path`", + "description": "###### What this is:\n*Average nodestore (NuDB) read latency: how long each on-disk object read takes. The direct disk-layer cost that backs up the job queue.*\n\n###### How it's computed:\n*rate(nodestore_state{metric=\"node_reads_duration_us\"}[$__rate_interval]) / rate(nodestore_state{metric=\"node_reads_total\"}[$__rate_interval]), in us.*\n\n###### Reading it:\n*Low single-digit us when the OS page cache is warm; tens-to-hundreds of us when reads hit the disk. Rises sharply during cold-cache catch-up.*\n\n###### Healthy range:\n*< ~10us/read warm; higher is expected briefly after a wipe/restart.*\n\n###### Watch for:\n*Sustained high us/read is the disk-bound signal on its own; the found ratio below stays near 100% even then, so do not wait for it to drop. Check EBS IOPS / io scheduler latency.*\n\n###### Keywords:\n- **NodeStore** *(per node)* \u2014 the key-value object store holding ledger data (tree nodes), backed by NuDB.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[nodestore/backend/NuDBFactory.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/nodestore/backend/NuDBFactory.cpp)\n\n###### Function:\n`NuDB backend read path`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#nodestore)", "type": "timeseries", "gridPos": { "h": 8, @@ -1269,7 +1269,7 @@ }, { "title": "I/O Scheduler Latency p95", - "description": "###### What this is:\n*95th-percentile latency of the node's internal I/O service queue - the async task scheduler that dispatches network and disk callbacks.*\n\n###### How it's computed:\n*histogram_quantile(0.95, rate(ios_latency_milliseconds_bucket[$__rate_interval])).*\n\n###### Reading it:\n*Low and flat when the event loop is responsive. Rising p95 means callbacks are queuing behind long-running work (often blocking disk reads).*\n\n###### Healthy range:\n*< ~10ms p95.*\n\n###### Watch for:\n*p95 climbing into hundreds of ms = the I/O service is saturated; the node cannot service network/disk events promptly, stalling sync.*\n\n###### Source:\n[core/impl/Workers.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/core/detail/Workers.cpp)\n\n###### Function:\n`io_service latency histogram`", + "description": "###### What this is:\n*95th-percentile latency of the node's internal I/O service queue - the async task scheduler that dispatches network and disk callbacks.*\n\n###### How it's computed:\n*histogram_quantile(0.95, rate(ios_latency_milliseconds_bucket[$__rate_interval])).*\n\n###### Reading it:\n*Low and flat when the event loop is responsive. Rising p95 means callbacks are queuing behind long-running work (often blocking disk reads).*\n\n###### Healthy range:\n*< ~10ms p95.*\n\n###### Watch for:\n*p95 climbing into hundreds of ms = the I/O service is saturated; the node cannot service network/disk events promptly, stalling sync.*\n\n###### Keywords:\n- **I/O scheduler** *(per node)* \u2014 the queue that serializes NodeStore disk reads and writes.\n- **NodeStore** *(per node)* \u2014 the key-value object store holding ledger data (tree nodes), backed by NuDB.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[core/impl/Workers.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/core/detail/Workers.cpp)\n\n###### Function:\n`io_service latency histogram`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#nodestore)", "type": "timeseries", "gridPos": { "h": 8, @@ -1311,7 +1311,7 @@ }, { "title": "NuDB Read Found Ratio", - "description": "###### What this is:\n*Fraction of nodestore fetches that found the object they asked for. This is not a cache hit ratio: `node_reads_hit` counts every fetch that returned an object, whatever served it, so a fetch that went all the way to disk still counts here.*\n\n###### How it's computed:\n*rate(nodestore_state{metric=\"node_reads_hit\"}[$__rate_interval]) / rate(nodestore_state{metric=\"node_reads_total\"}[$__rate_interval]).*\n\n###### Reading it:\n*Normally sits near 1.0 (100%) on any node that has the data, warm or cold, because a synced node almost always finds what it asks for. It does not fall when the page cache goes cold.*\n\n###### Healthy range:\n*Near 1.0. A ratio well below 1.0 means fetches are missing, which points at a gap in local history rather than at cache pressure.*\n\n###### Watch for:\n*Never read this panel on its own. A ~100% found ratio at over 100 microseconds per read is the cold-read signature, not a healthy cache: the data is found every time and paid for every time. Always pair it with NuDB Read Latency.*\n\n###### Source:\n[nodestore/Database.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/nodestore/Database.cpp)\n\n###### Function:\n`Database::fetchNodeObject`", + "description": "###### What this is:\n*Fraction of nodestore fetches that found the object they asked for. This is not a cache hit ratio: `node_reads_hit` counts every fetch that returned an object, whatever served it, so a fetch that went all the way to disk still counts here.*\n\n###### How it's computed:\n*rate(nodestore_state{metric=\"node_reads_hit\"}[$__rate_interval]) / rate(nodestore_state{metric=\"node_reads_total\"}[$__rate_interval]).*\n\n###### Reading it:\n*Normally sits near 1.0 (100%) on any node that has the data, warm or cold, because a synced node almost always finds what it asks for. It does not fall when the page cache goes cold.*\n\n###### Healthy range:\n*Near 1.0. A ratio well below 1.0 means fetches are missing, which points at a gap in local history rather than at cache pressure.*\n\n###### Watch for:\n*Never read this panel on its own. A ~100% found ratio at over 100 microseconds per read is the cold-read signature, not a healthy cache: the data is found every time and paid for every time. Always pair it with NuDB Read Latency.*\n\n###### Keywords:\n- **NodeStore** *(per node)* \u2014 the key-value object store holding ledger data (tree nodes), backed by NuDB.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[nodestore/Database.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/nodestore/Database.cpp)\n\n###### Function:\n`Database::fetchNodeObject`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#nodestore)", "type": "timeseries", "gridPos": { "h": 8, @@ -1353,7 +1353,7 @@ }, { "title": "NuDB Read Pressure", - "description": "###### What this is:\n*Nodestore read throughput and backlog: reads per second, the pending read queue depth, and how many read threads are active. Shows disk saturation depth.*\n\n###### How it's computed:\n*rate(node_reads_total), and gauges read_queue / read_threads_running from nodestore_state.*\n\n###### Reading it:\n*reads/s spikes during catch-up. A growing read_queue with all read threads busy = disk cannot keep up with demand.*\n\n###### Healthy range:\n*read_queue ~0 and reads/s low on a warm synced node.*\n\n###### Watch for:\n*read_queue climbing while read_threads_running is pinned at read_threads_total = disk-bound; the IOPS ceiling is the limiter.*\n\n###### Source:\n[nodestore/backend/NuDBFactory.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/nodestore/backend/NuDBFactory.cpp)\n\n###### Function:\n`NuDB read scheduler`", + "description": "###### What this is:\n*Nodestore read throughput and backlog: reads per second, the pending read queue depth, and how many read threads are active. Shows disk saturation depth.*\n\n###### How it's computed:\n*rate(node_reads_total), and gauges read_queue / read_threads_running from nodestore_state.*\n\n###### Reading it:\n*reads/s spikes during catch-up. A growing read_queue with all read threads busy = disk cannot keep up with demand.*\n\n###### Healthy range:\n*read_queue ~0 and reads/s low on a warm synced node.*\n\n###### Watch for:\n*read_queue climbing while read_threads_running is pinned at read_threads_total = disk-bound; the IOPS ceiling is the limiter.*\n\n###### Keywords:\n- **NodeStore** *(per node)* \u2014 the key-value object store holding ledger data (tree nodes), backed by NuDB.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[nodestore/backend/NuDBFactory.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/nodestore/backend/NuDBFactory.cpp)\n\n###### Function:\n`NuDB read scheduler`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#nodestore)", "type": "timeseries", "gridPos": { "h": 8, @@ -1442,7 +1442,7 @@ }, { "title": "Job Queue Depth", - "description": "###### What this is:\n*Total number of jobs currently queued across the JobQueue. A backlog means work is arriving faster than worker threads can drain it.*\n\n###### How it's computed:\n*jobq_job_count gauge (total queued jobs), per node.*\n\n###### Reading it:\n*Near 0 when keeping up. Sustained positive depth = the node is overloaded or blocked on a downstream resource (usually disk reads).*\n\n###### Healthy range:\n*~0 on a healthy node.*\n\n###### Watch for:\n*Depth climbing in step with Validated Ledger Age = the queue backlog is why the node is falling behind.*\n\n###### Source:\n[core/JobQueue.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/core/detail/JobQueue.cpp)\n\n###### Function:\n`JobQueue::getJobCountTotal`", + "description": "###### What this is:\n*Total number of jobs currently queued across the JobQueue. A backlog means work is arriving faster than worker threads can drain it.*\n\n###### How it's computed:\n*jobq_job_count gauge (total queued jobs), per node.*\n\n###### Reading it:\n*Near 0 when keeping up. Sustained positive depth = the node is overloaded or blocked on a downstream resource (usually disk reads).*\n\n###### Healthy range:\n*~0 on a healthy node.*\n\n###### Watch for:\n*Depth climbing in step with Validated Ledger Age = the queue backlog is why the node is falling behind.*\n\n###### Keywords:\n- **Job queue / job type** *(per node)* \u2014 xrpld's worker-thread pool; every unit of background work is enqueued under a named job type.\n- **Deferred job** *(per node)* \u2014 a job held back because its type is already at its concurrency limit; the leading indicator of queue backpressure.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[core/JobQueue.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/core/detail/JobQueue.cpp)\n\n###### Function:\n`JobQueue::getJobCountTotal`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#job-queue-job-type)", "type": "timeseries", "gridPos": { "h": 8, @@ -1484,7 +1484,7 @@ }, { "title": "Load Factor & Peers", - "description": "###### What this is:\n*Server load factor (fee/throttle multiplier; 1 = unloaded) alongside active inbound/outbound peer counts. Rules out overload and insufficient fetch sources as causes.*\n\n###### How it's computed:\n*load_factor_metrics{metric=\"load_factor\"}, and peer_finder_active_inbound_peers / _outbound_peers gauges.*\n\n###### Reading it:\n*load_factor at 1 = no local overload. Outbound peers should be healthy (~10+); too few peers limits how fast the node can fetch ledger data.*\n\n###### Healthy range:\n*load_factor = 1; outbound peers >= ~8.*\n\n###### Watch for:\n*load_factor > 1 = local overload throttling; very low peer count = not enough sources to acquire history from (a distinct sync bottleneck).*\n\n###### Source:\n[overlay/detail/OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl load / PeerFinder counts`", + "description": "###### What this is:\n*Server load factor (fee/throttle multiplier; 1 = unloaded) alongside active inbound/outbound peer counts. Rules out overload and insufficient fetch sources as causes.*\n\n###### How it's computed:\n*load_factor_metrics{metric=\"load_factor\"}, and peer_finder_active_inbound_peers / _outbound_peers gauges.*\n\n###### Reading it:\n*load_factor at 1 = no local overload. Outbound peers should be healthy (~10+); too few peers limits how fast the node can fetch ledger data.*\n\n###### Healthy range:\n*load_factor = 1; outbound peers >= ~8.*\n\n###### Watch for:\n*load_factor > 1 = local overload throttling; very low peer count = not enough sources to acquire history from (a distinct sync bottleneck).*\n\n###### Keywords:\n- **Load factor** *(per node)* \u2014 the multiplier the node applies to the reference transaction fee when under load.\n- **Transaction queue (TxQ)** *(per node)* \u2014 holds transactions that cannot enter the open ledger yet, ordered by fee level.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[overlay/detail/OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl load / PeerFinder counts`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#load-factor)", "type": "timeseries", "gridPos": { "h": 8, @@ -1553,7 +1553,7 @@ }, { "title": "Job Queue Backlog and Deferred by Type", - "description": "###### What this is:\n*Per-job-type queue depth, two series per type. Waiting is the whole backlog: every job enqueued for that type that has not started yet. Deferred is the subset of that backlog that is blocked specifically because the type is already running at its concurrency limit. Deferred is the leading indicator of backpressure, because JobQueue::addJob never rejects a job -- it always returns success and defers instead, so a capped type under pressure produces no error and no dropped work. Without these the only evidence is latency, which appears after the harm is already done.*\n\n###### How it's computed:\n*Two targets, each the top 10 gauges by current value: jobq__waiting and jobq__deferred. Both are live depths published by JobQueue::collect() under the same mutex that guards the counters, so the pair is always read at the same instant and is directly comparable. Gauges exist only for non-special job types, so the 11 special types -- the ones declared with a limit of 0, which bypass the limit logic entirely and therefore never defer -- do not appear on either series.*\n\n###### Reading it:\n*Read the two together; the ratio is the diagnostic, not either value alone. Deferred is always a subset of waiting, because addRefCountedJob increments waiting for every job and deferred only for the ones that arrive while the type is at its limit. Waiting high with deferred at zero means the type has spare slots and the backlog is just arrival burstiness -- it will drain without intervention. Waiting high with deferred also high means the concurrency limit is the binding constraint, not the work. Both near zero is the normal state. These are depths, not rates: the value is how many jobs are queued right now. finishJob drains deferred one per completion, so a deferred line that stays elevated means arrivals are outpacing completions rather than one isolated burst. Only the 10 highest series per state are drawn, which on an idle node is arbitrary among the zeros and under load is exactly the types under pressure.*\n\n###### Healthy range:\n*Deferred zero on all types. Waiting near zero, with brief spikes during ledger close.*\n\n###### Watch for:\n*ledgerrequest deferred above zero: the 3-slot ledgerRequest queue is full, so TMGetLedger service to syncing peers is being delayed. Use LedgerReq Wait by Handler next to see which of its two producers is responsible. ledgerdata or fetchtxndata deferred: inbound ledger data cannot be absorbed fast enough, which is what makes validated ledger age grow. A waiting line that climbs steadily while deferred stays flat points at the worker pool or at slow jobs rather than at the limit. Note both are sampled by the collector hook once per interval, so a sub-second spike can be missed; a reading of zero is not proof that nothing was ever queued or deferred.*\n\n###### Source:\n[core/JobQueue.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/core/detail/JobQueue.cpp)\n\n###### Function:\n`JobQueue::addRefCountedJob / JobQueue::collect`", + "description": "###### What this is:\n*Per-job-type queue depth, two series per type. Waiting is the whole backlog: every job enqueued for that type that has not started yet. Deferred is the subset of that backlog that is blocked specifically because the type is already running at its concurrency limit. Deferred is the leading indicator of backpressure, because JobQueue::addJob never rejects a job for queue pressure -- it returns success and defers instead, so a capped type under pressure produces no error and no dropped work. Without these the only evidence is latency, which appears after the harm is already done.*\n\n###### How it's computed:\n*Two targets, each the top 10 gauges by current value: jobq__waiting and jobq__deferred. JobQueue::collect snapshots both counters under the one lock that guards them, so the pair is read at the same instant and is directly comparable, then publishes them on the 1-second export cycle. Gauges exist only for non-special job types, so the 11 special types -- the ones declared with a limit of 0, which bypass the limit logic entirely and therefore never defer -- do not appear on either series.*\n\n###### Reading it:\n*Read the two together; the ratio is the diagnostic, not either value alone. Deferred is always a subset of waiting, because addRefCountedJob increments waiting for every job and deferred only for the ones that arrive while the type is at its limit. Waiting high with deferred at zero means the type has spare slots and the backlog is just arrival burstiness -- it will drain without intervention. Waiting high with deferred also high means the concurrency limit is the binding constraint, not the work. Both near zero is the normal state. These are depths, not rates: the value is how many jobs are queued right now. finishJob drains deferred one per completion, so a deferred line that stays elevated means arrivals are outpacing completions rather than one isolated burst. Only the 10 highest series per state are drawn, which on an idle node is arbitrary among the zeros and under load is exactly the types under pressure.*\n\n###### Healthy range:\n*Deferred zero on all types. Waiting near zero, with brief spikes during ledger close.*\n\n###### Watch for:\n*ledgerrequest deferred above zero: the 3-slot ledgerRequest queue is full, so TMGetLedger service to syncing peers is being delayed. Use LedgerReq Wait by Handler next to see which of its two producers is responsible. ledgerdata or fetchtxndata deferred: inbound ledger data cannot be absorbed fast enough, which is what makes validated ledger age grow. A waiting line that climbs steadily while deferred stays flat points at the worker pool or at slow jobs rather than at the limit. Note both are sampled once per export cycle, so a sub-second spike can be missed; a reading of zero is not proof that nothing was ever queued or deferred.*\n\n###### Keywords:\n- **Deferred job** *(per node)* \u2014 a job held back because its type is already at its concurrency limit; the leading indicator of queue backpressure.\n- **Concurrency limit** *(per node)* \u2014 the cap on how many jobs of one type may run at once; a type at its cap cannot start more work.\n- **Job queue / job type** *(per node)* \u2014 xrpld's worker-thread pool; every unit of background work is enqueued under a named job type.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[core/JobQueue.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/core/detail/JobQueue.cpp)\n\n###### Function:\n`JobQueue::addRefCountedJob / JobQueue::collect`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#deferred-job)", "type": "timeseries", "gridPos": { "h": 8, @@ -1566,11 +1566,6 @@ "maxHeight": 600, "mode": "multi", "sort": "desc" - }, - "legend": { - "displayMode": "table", - "placement": "right", - "calcs": ["mean", "max"] } }, "targets": [ @@ -1607,7 +1602,7 @@ }, { "title": "LedgerReq Wait by Handler", - "description": "###### What this is:\n*Queue wait for the ledgerRequest job type, split by which handler enqueued the job. The type has a concurrency limit of 3 and two producers that compete for those slots: RcvGetLedger, which serves TMGetLedger to syncing peers, and RcvGetObjByHash, which serves TMGetObjectByHash. Both report the same job_type, so without the handler split a wait spike cannot be attributed to either.*\n\n###### How it's computed:\n*p99 of job_queued_us for job_type=\"ledgerRequest\", grouped by the handler label. The handler value is the addJob name passed through a sanitiser that keeps letters-only names and folds anything else to \"other\", which bounds the label domain.*\n\n###### Reading it:\n*This is the panel that answers which producer is starving the 3-slot queue. Both lines high together means the queue is genuinely oversubscribed and both kinds of peer request are being delayed. One line high while the other is flat means that producer is arriving faster than 3 concurrent slots can absorb, and it is the one delaying the other. Wait is queue time only, so a high line here is contention, not slow work; the work itself is on the GetObject Handler Latency Breakdown panel.*\n\n###### Healthy range:\n*Single-digit to low-tens of milliseconds p99 for both handlers, matching the wider Job Queue Wait p95 By Type panel.*\n\n###### Watch for:\n*RcvGetObjByHash wait climbing: one in-bounds TMGetObjectByHash request can perform thousands of NodeStore lookups, so a few concurrent ones occupy every slot and delay TMGetLedger to peers that are themselves syncing. Cross-check Job Queue Backlog and Deferred by Type for jobq_ledgerrequest_deferred above zero to confirm the limit, not the work, is the binding constraint.*\n\n###### Source:\n[core/JobQueue.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/core/detail/JobQueue.cpp)\n\n###### Function:\n`JobQueue::processTask -> PerfLog::jobStart`", + "description": "###### What this is:\n*Queue wait for the ledgerRequest job type, split by which handler enqueued the job. The type has a concurrency limit of 3 and two producers that compete for those slots: RcvGetLedger, which serves TMGetLedger to syncing peers, and RcvGetObjByHash, which serves TMGetObjectByHash. Both report the same job_type, so without the handler split a wait spike cannot be attributed to either.*\n\n###### How it's computed:\n*p99 of job_queued_us for job_type=\"ledgerRequest\", grouped by the handler label. JobQueue::processTask measures the wait, then PerfLog hands it to MetricsRegistry::recordJobStarted, which is where the histogram is recorded. The handler value is the addJob name passed through a sanitizer that keeps letters-only names and folds everything else to \"other\", which bounds the label domain to 43 names plus \"other\". Both producers here are letters-only, so both appear under their own names; \"other\" is a mixed bucket and never means one specific caller.*\n\n###### Reading it:\n*This is the panel that answers which producer is starving the 3-slot queue. Both lines high together means the queue is genuinely oversubscribed and both kinds of peer request are being delayed. One line high while the other is flat means that producer is arriving faster than 3 concurrent slots can absorb, and it is the one delaying the other. Wait is queue time only, so a high line here is contention, not slow work; the work itself is on the GetObject Handler Latency Breakdown panel.*\n\n###### Healthy range:\n*Single-digit to low-tens of milliseconds p99 for both handlers, matching the wider Job Queue Wait p95 By Type panel.*\n\n###### Watch for:\n*RcvGetObjByHash wait climbing: one in-bounds TMGetObjectByHash request can perform thousands of NodeStore lookups, so a few concurrent ones occupy every slot and delay TMGetLedger to peers that are themselves syncing. Cross-check Job Queue Backlog and Deferred by Type for jobq_ledgerrequest_deferred above zero to confirm the limit, not the work, is the binding constraint.*\n\n###### Keywords:\n- **Handler label** *(per node)* \u2014 the addJob call-site name attached to job metrics, so producers sharing one job type stay separable.\n- **Concurrency limit** *(per node)* \u2014 the cap on how many jobs of one type may run at once; a type at its cap cannot start more work.\n- **Job queue / job type** *(per node)* \u2014 xrpld's worker-thread pool; every unit of background work is enqueued under a named job type.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`MetricsRegistry::recordJobStarted`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#handler-label)", "type": "timeseries", "gridPos": { "h": 8, @@ -1636,7 +1631,7 @@ "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "\u00b5s", "custom": { - "axisLabel": "p99 Wait (\u03bcs)", + "axisLabel": "p99 Wait (\u00b5s)", "spanNulls": 1800000, "insertNulls": false, "showPoints": "auto", @@ -1662,7 +1657,7 @@ }, { "title": "NodeStore Read Latency (Bottleneck Discriminator)", - "description": "###### What this is:\n*The single measurement that separates the two ways a ledger sync stalls. Both modes look identical from the job queue -- the ledgerData lane sits at its concurrency limit of 3 with work waiting -- so lane occupancy on its own diagnoses nothing. Read latency does separate them, because the two modes load opposite ends of the storage path.*\n\n###### How it's computed:\n*Two views of the same quantity. Lifetime is the nodestore_state{metric=\"read_mean_us\"} gauge, which is total fetch microseconds divided by total fetches since process start, so it moves slowly and shows the run as a whole. Windowed is rate(node_reads_duration_us) / rate(node_reads_total) over the panel interval, so it reacts within one scrape. The axis is logarithmic because the two modes differ by more than an order of magnitude and a linear axis flattens the lower one.*\n\n###### Reading it:\n*Below about 10 microseconds per read means the backend is answering from the page cache and reads are not the constraint; if the lane is still full, the cost is on the write side -- check NuDB Writer Queue Depth next. Above about 100 microseconds per read, together with a high found ratio on NuDB Read Found Ratio, means objects are being found but paid for with disk latency on every access. High latency with a low found ratio is a different thing again: the working set does not fit and misses are scanning every backend. The threshold lines at 10 and 100 mark the two boundaries.*\n\n###### Healthy range:\n*Single-digit microseconds per read on a warm synced node, with lifetime and windowed close together.*\n\n###### Watch for:\n*Windowed rising well above lifetime: the recent window is much worse than the run average, which is the earliest reading of a cache that has just gone cold. A measured cold-read episode peaked at 223 microseconds and settled in the 13-35 range while still hitting 88 percent or better, and that shape hung a node for roughly 25 minutes. Also treat a flat 8-9 microsecond line during a stall as informative rather than reassuring -- it rules reads out and points at the write path.*\n\n###### Source:\n[nodestore/backend/NuDBFactory.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/nodestore/backend/NuDBFactory.cpp)\n\n###### Function:\n`Database::getFetchDurationUs / NuDB backend read path`", + "description": "###### What this is:\n*The single measurement that separates the two ways a ledger sync stalls. Both modes look identical from the job queue -- the ledgerData lane sits at its concurrency limit of 3 with work waiting -- so lane occupancy on its own diagnoses nothing. Read latency does separate them, because the two modes load opposite ends of the storage path.*\n\n###### How it's computed:\n*Two views of the same quantity. Lifetime is the nodestore_state{metric=\"read_mean_us\"} gauge, which is total fetch microseconds divided by total fetches since process start, so it moves slowly and shows the run as a whole. Windowed is rate(node_reads_duration_us) / rate(node_reads_total) over the panel interval, so it reacts within one scrape. The axis is logarithmic because the two modes differ by more than an order of magnitude and a linear axis flattens the lower one.*\n\n###### Reading it:\n*Below about 10 microseconds per read means the backend is answering from the page cache and reads are not the constraint; if the lane is still full, the cost is on the write side -- check NuDB Writer Queue Depth next. Above about 100 microseconds per read, together with a high found ratio on NuDB Read Found Ratio, means objects are being found but paid for with disk latency on every access. High latency with a low found ratio is a different thing again: the working set does not fit and misses are scanning every backend. The threshold lines at 10 and 100 mark the two boundaries.*\n\n###### Healthy range:\n*Single-digit microseconds per read on a warm synced node, with lifetime and windowed close together.*\n\n###### Watch for:\n*Windowed rising well above lifetime: the recent window is much worse than the run average, which is the earliest reading of a cache that has just gone cold. A measured cold-read episode peaked at 223 microseconds and settled in the 13-35 range while still hitting 88 percent or better, and that shape hung a node for roughly 25 minutes. Also treat a flat 8-9 microsecond line during a stall as informative rather than reassuring -- it rules reads out and points at the write path.*\n\n###### Keywords:\n- **NodeStore** *(per node)* \u2014 the key-value object store holding ledger data (tree nodes), backed by NuDB.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[nodestore/backend/NuDBFactory.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/nodestore/backend/NuDBFactory.cpp)\n\n###### Function:\n`Database::getFetchDurationUs / NuDB backend read path`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#nodestore)", "type": "timeseries", "gridPos": { "h": 8, @@ -1734,7 +1729,7 @@ }, { "title": "NuDB Writer Queue Depth", - "description": "###### What this is:\n*How many writers are queued at NuDB's insert mutex. NuDB takes one global lock per insert, so concurrent writers do not overlap -- they line up. This panel is the confirming half of the read-latency discriminator: when reads are fast and the lane is still full, the queueing is here.*\n\n###### How it's computed:\n*Mean Depth is nodestore_state{metric=\"nudb_writer_depth_x100\"} divided by 100. At the source it is depthSum over depthSamples, both accumulated when an insert enters the critical section, so an insert still in flight is part of the mean. The exported gauge is integral, so the mean is scaled by 100 to keep the fractional part; dividing it back is what makes 1.60 readable instead of 1. In Flight is the instantaneous nudb_writers_in_flight sample.*\n\n###### Reading it:\n*Depth is a queue length, so 1.0 is the floor and means every insert found the lock free. Anything meaningfully above 1.0 means inserts are waiting on each other and the write path, not the disk, is setting the pace. In Flight is a point sample from the scrape instant and will look spikier than the mean; read the mean for the trend and In Flight for the peak.*\n\n###### Healthy range:\n*Mean depth at or just above 1.0, In Flight low.*\n\n###### Watch for:\n*Mean depth above 1 while read latency stays in single-digit microseconds -- that pair is the write-lock ceiling, and no amount of read tuning will move it. A measured run of this mode held depth at 1.60 with reads flat at 8-9 microseconds and took 510 seconds to reach full. That 1.60 came from a build whose sample count advanced at insert exit rather than entry, which biased the mean down, so treat it as a lower bound. The same run appeared to complete nothing, which was a separate counting defect, not a stalled node. Depth at 1.0 with slow reads is the opposite mode; look at the read panel.*\n\n###### Source:\n[telemetry/MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`MetricsRegistry::observeWritePathDetail`", + "description": "###### What this is:\n*How many writers are queued at NuDB's insert mutex. NuDB takes one global lock per insert, so concurrent writers do not overlap -- they line up. This panel is the confirming half of the read-latency discriminator: when reads are fast and the lane is still full, the queueing is here.*\n\n###### How it's computed:\n*Mean Depth is nodestore_state{metric=\"nudb_writer_depth_x100\"} divided by 100. At the source it is depthSum over depthSamples, both accumulated when an insert enters the critical section, so an insert still in flight is part of the mean. The exported gauge is integral, so the mean is scaled by 100 to keep the fractional part; dividing it back is what makes 1.60 readable instead of 1. In Flight is the instantaneous nudb_writers_in_flight sample.*\n\n###### Reading it:\n*Depth is a queue length, so 1.0 is the floor and means every insert found the lock free. Anything meaningfully above 1.0 means inserts are waiting on each other and the write path, not the disk, is setting the pace. In Flight is a point sample from the scrape instant and will look spikier than the mean; read the mean for the trend and In Flight for the peak.*\n\n###### Healthy range:\n*Mean depth at or just above 1.0, In Flight low.*\n\n###### Watch for:\n*Mean depth above 1 while read latency stays in single-digit microseconds -- that pair is the write-lock ceiling, and no amount of read tuning will move it. A measured run of this mode held depth at 1.60 with reads flat at 8-9 microseconds and took 510 seconds to reach full. That 1.60 came from a build whose sample count advanced at insert exit rather than entry, which biased the mean down, so treat it as a lower bound. The same run appeared to complete nothing, which was a separate counting defect, not a stalled node. Depth at 1.0 with slow reads is the opposite mode; look at the read panel.*\n\n###### Keywords:\n- **NodeStore** *(per node)* \u2014 the key-value object store holding ledger data (tree nodes), backed by NuDB.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[telemetry/MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`MetricsRegistry::observeWritePathDetail`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#nodestore)", "type": "timeseries", "gridPos": { "h": 8, @@ -1783,7 +1778,7 @@ }, { "title": "NuDB Insert Time (Mean & Max)", - "description": "###### What this is:\n*Time spent inside a NuDB insert, mean and worst case, alongside the backend's overall mean write latency. Splits an insert's cost into the part that is real work and the part that is waiting for the global insert mutex.*\n\n###### How it's computed:\n*Insert Mean is nodestore_state{metric=\"nudb_insert_mean_us\"}, total insert microseconds over insert count. Insert Max is nudb_insert_max_us, a true running maximum rather than a quantile, so one bad insert is visible and never averaged away. Write Mean is the write_mean_us gauge for the whole backend write path.*\n\n###### Reading it:\n*Compare Insert Mean against the service time implied by Writer Queue Depth. Mean insert time above the unqueued service time is wait, and the gap is the fraction of every write spent queued rather than working. Insert Max far above Insert Mean means the distribution has a tail -- typically a bucket split or a commit -- which a mean alone hides.*\n\n###### Healthy range:\n*Insert Mean in the low tens of microseconds with Insert Max within roughly an order of magnitude of it.*\n\n###### Watch for:\n*A widening gap between Insert Mean and the service floor. In a measured write-lock-bound run the mean was 20 microseconds of which only 12.5 was service, leaving 7.5 -- at least 37 percent of every insert -- as pure queueing. It is a floor rather than an exact figure because it is derived from a mean depth that the build of the day biased downward, and a larger depth implies a larger queueing share. That is the cost the writer-depth panel predicts, quantified. Insert Max climbing on its own without the mean moving is a tail problem, not a saturation problem.*\n\n###### Source:\n[telemetry/MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`MetricsRegistry::observeWritePathDetail`", + "description": "###### What this is:\n*Time spent inside a NuDB insert, mean and worst case, alongside the backend's overall mean write latency. Splits an insert's cost into the part that is real work and the part that is waiting for the global insert mutex.*\n\n###### How it's computed:\n*Insert Mean is nodestore_state{metric=\"nudb_insert_mean_us\"}, total insert microseconds over insert count. Insert Max is nudb_insert_max_us, a true running maximum rather than a quantile, so one bad insert is visible and never averaged away. Write Mean is the write_mean_us gauge for the whole backend write path.*\n\n###### Reading it:\n*Compare Insert Mean against the service time implied by Writer Queue Depth. Mean insert time above the unqueued service time is wait, and the gap is the fraction of every write spent queued rather than working. Insert Max far above Insert Mean means the distribution has a tail -- typically a bucket split or a commit -- which a mean alone hides.*\n\n###### Healthy range:\n*Insert Mean in the low tens of microseconds with Insert Max within roughly an order of magnitude of it.*\n\n###### Watch for:\n*A widening gap between Insert Mean and the service floor. In a measured write-lock-bound run the mean was 20 microseconds of which only 12.5 was service, leaving 7.5 -- at least 37 percent of every insert -- as pure queueing. It is a floor rather than an exact figure because it is derived from a mean depth that the build of the day biased downward, and a larger depth implies a larger queueing share. That is the cost the writer-depth panel predicts, quantified. Insert Max climbing on its own without the mean moving is a tail problem, not a saturation problem.*\n\n###### Keywords:\n- **NodeStore** *(per node)* \u2014 the key-value object store holding ledger data (tree nodes), backed by NuDB.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[telemetry/MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`MetricsRegistry::observeWritePathDetail`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#nodestore)", "type": "timeseries", "gridPos": { "h": 8, @@ -1843,7 +1838,7 @@ }, { "title": "Acquire Deferrals vs Timeouts (All Lanes)", - "description": "###### What this is:\n*The two counters that together identify an acquisition livelock, summed over every acquisition lane. Deferrals count timer jobs that were skipped because the lane was already at its limit. Timeouts count timer bodies that actually ran and advanced an acquisition's retry counter. Both series are on one panel deliberately: the signal is the divergence between them, and neither counter shows it alone. Both are recorded in TimeoutCounter, a base shared by five subclasses with different job limits, so this pair pools every lane -- read the ledger-scoped panel for a diagnosis and this one only to ask whether any lane is deferring.*\n\n###### How it's computed:\n*rate() over nodestore_state{metric=\"acquire_deferrals\"} and nodestore_state{metric=\"acquire_timeouts\"}. Both are cumulative counters, so the rate is the per-second event frequency and a restart shows as a gap rather than as a negative spike.*\n\n###### Reading it:\n*Deferrals rising while timeouts stay flat is the livelock fingerprint, but on this pair it does not say WHICH lane. Retry counts only advance when a timer body runs, so if every timer is being deferred instead, the retry budget never advances and the give-up path is effectively disarmed -- the acquisition can neither finish nor fail, and it holds its slot indefinitely. The two rates moving together is the benign case: the lane is busy but timers are still landing and acquisitions are still progressing toward either success or abandonment.*\n\n###### Healthy range:\n*Both near zero when synced. During catch-up, deferrals non-zero is expected, but timeouts should track rather than flatten.*\n\n###### Watch for:\n*A deferral rate that climbs while the timeout rate stays pinned -- then confirm on Ledger Acquire Deferrals vs Timeouts before calling it a ledger-acquisition stall, because a saturated replay lane produces the same shape here. One measured stall recorded 5441 deferrals against 687 timeouts -- an eight-to-one ratio -- but those were all-lane counts and cannot be attributed to ledger acquisition. Cross-check Acquisition Progress: a flat completion rate confirms the acquisitions are stuck rather than merely slow.*\n\n###### Source:\n[app/ledger/InboundLedgers.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/detail/InboundLedgers.cpp)\n\n###### Function:\n`InboundLedgers acquire retry timer / AcquireStats`", + "description": "###### What this is:\n*The two counters that together identify an acquisition livelock, summed over every acquisition lane. Deferrals count timer jobs that were skipped because the lane was already at its limit. Timeouts count timer bodies that actually ran and advanced an acquisition's retry counter. Both series are on one panel deliberately: the signal is the divergence between them, and neither counter shows it alone. Both are recorded in TimeoutCounter, a base shared by five subclasses with different job limits, so this pair pools every lane -- read the ledger-scoped panel for a diagnosis and this one only to ask whether any lane is deferring.*\n\n###### How it's computed:\n*rate() over nodestore_state{metric=\"acquire_deferrals\"} and nodestore_state{metric=\"acquire_timeouts\"}. Both are cumulative counters, so the rate is the per-second event frequency and a restart shows as a gap rather than as a negative spike.*\n\n###### Reading it:\n*Deferrals rising while timeouts stay flat is the livelock fingerprint, but on this pair it does not say WHICH lane. Retry counts only advance when a timer body runs, so if every timer is being deferred instead, the retry budget never advances and the give-up path is effectively disarmed -- the acquisition can neither finish nor fail, and it holds its slot indefinitely. The two rates moving together is the benign case: the lane is busy but timers are still landing and acquisitions are still progressing toward either success or abandonment.*\n\n###### Healthy range:\n*Both near zero when synced. During catch-up, deferrals non-zero is expected, but timeouts should track rather than flatten.*\n\n###### Watch for:\n*A deferral rate that climbs while the timeout rate stays pinned -- then confirm on Ledger Acquire Deferrals vs Timeouts before calling it a ledger-acquisition stall, because a saturated replay lane produces the same shape here. One measured stall recorded 5441 deferrals against 687 timeouts -- an eight-to-one ratio -- but those were all-lane counts and cannot be attributed to ledger acquisition. Cross-check Acquisition Progress: a flat completion rate confirms the acquisitions are stuck rather than merely slow.*\n\n###### Keywords:\n- **NodeStore** *(per node)* \u2014 the key-value object store holding ledger data (tree nodes), backed by NuDB.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[app/ledger/InboundLedgers.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/detail/InboundLedgers.cpp)\n\n###### Function:\n`InboundLedgers acquire retry timer / AcquireStats`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#nodestore)", "type": "timeseries", "gridPos": { "h": 8, @@ -1892,7 +1887,7 @@ }, { "title": "Acquisition Progress (Completions, Give-Ups & Aborts)", - "description": "###### What this is:\n*Whether ledger acquisitions are reaching an ending at all. Completions are acquisitions that finished with the data. Give-Ups are acquisitions that exhausted their retry budget. Aborts are acquisitions destroyed before either outcome. Together they are every way an acquisition can leave the system, so the sum going to zero while work is queued means nothing is leaving.*\n\n###### How it's computed:\n*rate() over the cumulative nodestore_state{metric=\"acquire_completions\"}, acquire_give_ups and acquire_aborts counters. Completions cover both ways an acquisition can finish: the normal done() path and the init() path satisfied entirely from the local store.*\n\n###### Reading it:\n*This panel is the outcome side of the deferral panel. A healthy catch-up shows a steady completion rate; a healthy failure shows give-ups. What should never happen is all three flat while the ledgerData lane is full, because that means acquisitions are occupying slots without ever resolving. Read this together with Deferrals vs Timeouts: deferrals climbing with completions at zero narrows the stall down to the retry path.*\n\n###### Healthy range:\n*Completion rate positive whenever ledgers are being acquired. Give-ups and aborts low.*\n\n###### Watch for:\n*Zero completions sustained while the lane is busy -- on a current build. A measured write-bound run recorded zero completions across 510 seconds, but that run predates the fix that counts acquisitions satisfied from the local store, which were previously never counted at all; the node did reach full, so the zero was the counter and not the node. Completions are now counted at both exits behind an idempotent latch, so a zero here means zero. A rising give-up rate is less alarming than a flat one -- it at least means the retry budget is being consumed and slots are being returned.*\n\n###### Source:\n[app/ledger/InboundLedgers.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/detail/InboundLedgers.cpp)\n\n###### Function:\n`InboundLedger completion / AcquireStats`", + "description": "###### What this is:\n*Whether ledger acquisitions are reaching an ending at all. Completions are acquisitions that finished with the data. Give-Ups are acquisitions that exhausted their retry budget. Aborts are acquisitions destroyed before either outcome. Together they are every way an acquisition can leave the system, so the sum going to zero while work is queued means nothing is leaving.*\n\n###### How it's computed:\n*rate() over the cumulative nodestore_state{metric=\"acquire_completions\"}, acquire_give_ups and acquire_aborts counters. Completions cover both ways an acquisition can finish: the normal done() path and the init() path satisfied entirely from the local store.*\n\n###### Reading it:\n*This panel is the outcome side of the deferral panel. A healthy catch-up shows a steady completion rate; a healthy failure shows give-ups. What should never happen is all three flat while the ledgerData lane is full, because that means acquisitions are occupying slots without ever resolving. Read this together with Deferrals vs Timeouts: deferrals climbing with completions at zero narrows the stall down to the retry path.*\n\n###### Healthy range:\n*Completion rate positive whenever ledgers are being acquired. Give-ups and aborts low.*\n\n###### Watch for:\n*Zero completions sustained while the lane is busy -- on a current build. A measured write-bound run recorded zero completions across 510 seconds, but that run predates the fix that counts acquisitions satisfied from the local store, which were previously never counted at all; the node did reach full, so the zero was the counter and not the node. Completions are now counted at both exits behind an idempotent latch, so a zero here means zero. A rising give-up rate is less alarming than a flat one -- it at least means the retry budget is being consumed and slots are being returned.*\n\n###### Keywords:\n- **NodeStore** *(per node)* \u2014 the key-value object store holding ledger data (tree nodes), backed by NuDB.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[app/ledger/InboundLedgers.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/detail/InboundLedgers.cpp)\n\n###### Function:\n`InboundLedger completion / AcquireStats`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#nodestore)", "type": "timeseries", "gridPos": { "h": 8, @@ -1948,7 +1943,7 @@ }, { "title": "Discarded Acquire Work (Sweeps & Partial Aborts)", - "description": "###### What this is:\n*Work that was fetched and then thrown away. Sweep Evictions are acquisitions removed by the one-minute idle sweep. Partial Aborts are the subset of aborted acquisitions that had already built part of a map when they were destroyed, so the bytes fetched for them were wasted.*\n\n###### How it's computed:\n*rate() over the cumulative nodestore_state{metric=\"acquire_sweep_evictions\"} and acquire_aborts_partial counters.*\n\n###### Reading it:\n*Sweep evictions are the sweeper reclaiming acquisitions that stopped making progress, so a sustained non-zero rate means acquisitions are going idle rather than finishing -- it is the sweeper cleaning up after the stall on the adjacent panels, not a cause of its own. Partial aborts quantify the waste: each one is fetch bandwidth and nodestore writes spent on a map that was discarded, which then has to be fetched again.*\n\n###### Healthy range:\n*Both at or near zero.*\n\n###### Watch for:\n*A sweep-eviction rate that persists after the sync should have settled -- acquisitions are being started and abandoned in a loop, and each cycle re-pays the fetch cost. Two measured runs of the same duration differed sharply here, 127 sweeps in the write-bound case against 38 in the read-bound one, so a high sweep count is itself weak evidence for the write-bound mode. Confirm with read latency and writer depth before acting on it.*\n\n###### Source:\n[app/ledger/InboundLedgers.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/detail/InboundLedgers.cpp)\n\n###### Function:\n`InboundLedgers::sweep / AcquireStats`", + "description": "###### What this is:\n*Work that was fetched and then thrown away. Sweep Evictions are acquisitions removed by the one-minute idle sweep. Partial Aborts are the subset of aborted acquisitions that had already built part of a map when they were destroyed, so the bytes fetched for them were wasted.*\n\n###### How it's computed:\n*rate() over the cumulative nodestore_state{metric=\"acquire_sweep_evictions\"} and acquire_aborts_partial counters.*\n\n###### Reading it:\n*Sweep evictions are the sweeper reclaiming acquisitions that stopped making progress, so a sustained non-zero rate means acquisitions are going idle rather than finishing -- it is the sweeper cleaning up after the stall on the adjacent panels, not a cause of its own. Partial aborts quantify the waste: each one is fetch bandwidth and nodestore writes spent on a map that was discarded, which then has to be fetched again.*\n\n###### Healthy range:\n*Both at or near zero.*\n\n###### Watch for:\n*A sweep-eviction rate that persists after the sync should have settled -- acquisitions are being started and abandoned in a loop, and each cycle re-pays the fetch cost. Two measured runs of the same duration differed sharply here, 127 sweeps in the write-bound case against 38 in the read-bound one, so a high sweep count is itself weak evidence for the write-bound mode. Confirm with read latency and writer depth before acting on it.*\n\n###### Keywords:\n- **NodeStore** *(per node)* \u2014 the key-value object store holding ledger data (tree nodes), backed by NuDB.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[app/ledger/InboundLedgers.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/detail/InboundLedgers.cpp)\n\n###### Function:\n`InboundLedgers::sweep / AcquireStats`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#nodestore)", "type": "timeseries", "gridPos": { "h": 8, @@ -1997,7 +1992,7 @@ }, { "title": "Ledger Acquire Deferrals vs Timeouts (Ledger Lane Only)", - "description": "###### What this is:\n*The deferral and timeout counters narrowed to ledger acquisition alone. The all-lane pair on the panel above sums every TimeoutCounter subclass -- inbound ledgers, transaction sets and the three ledger-replay tasks -- each with its own job limit, so a saturated replay lane reproduces the livelock shape while ledger acquisition is healthy. These two count the same events for the InboundLedger lane only, so a divergence here is about ledger acquisition and nothing else.*\n\n###### How it's computed:\n*rate() over nodestore_state{metric=\"acquire_ledger_deferrals\"} and nodestore_state{metric=\"acquire_ledger_timeouts\"}. Both are cumulative counters incremented only when the recording TimeoutCounter's job name is InboundLedger, so the rate is the per-second event frequency for that lane and a restart shows as a gap rather than a negative spike.*\n\n###### Reading it:\n*Same fingerprint as the all-lane panel, but trustworthy: deferrals rising while timeouts stay flat means ledger acquisition's retry budget is not advancing, so the give-up path cannot fire and an acquisition holds its slot without finishing or failing. The two rates moving together is benign -- the lane is busy but timers are landing. Read this panel first and treat the all-lane panel as context for whether some other lane is also under pressure.*\n\n###### Healthy range:\n*Both near zero when synced. During catch-up, deferrals non-zero is expected, but timeouts should track rather than flatten.*\n\n###### Watch for:\n*A ledger deferral rate that climbs while the ledger timeout rate stays pinned. Compare against the all-lane panel: if the all-lane pair diverges but these two do not, the stall is in another acquisition lane and ledger acquisition is not the problem. Cross-check Acquisition Progress -- a flat completion rate alongside a divergence here confirms the acquisitions are stuck rather than merely slow.*\n\n###### Source:\n[app/ledger/detail/TimeoutCounter.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/detail/TimeoutCounter.cpp)\n\n###### Function:\n`TimeoutCounter::queueJob, TimeoutCounter::invokeOnTimer (via isLedgerAcquisition) / AcquireStats`", + "description": "###### What this is:\n*The deferral and timeout counters narrowed to ledger acquisition alone. The all-lane pair on the panel above sums every TimeoutCounter subclass -- inbound ledgers, transaction sets and the three ledger-replay tasks -- each with its own job limit, so a saturated replay lane reproduces the livelock shape while ledger acquisition is healthy. These two count the same events for the InboundLedger lane only, so a divergence here is about ledger acquisition and nothing else.*\n\n###### How it's computed:\n*rate() over nodestore_state{metric=\"acquire_ledger_deferrals\"} and nodestore_state{metric=\"acquire_ledger_timeouts\"}. Both are cumulative counters incremented only when the recording TimeoutCounter's job name is InboundLedger, so the rate is the per-second event frequency for that lane and a restart shows as a gap rather than a negative spike.*\n\n###### Reading it:\n*Same fingerprint as the all-lane panel, but trustworthy: deferrals rising while timeouts stay flat means ledger acquisition's retry budget is not advancing, so the give-up path cannot fire and an acquisition holds its slot without finishing or failing. The two rates moving together is benign -- the lane is busy but timers are landing. Read this panel first and treat the all-lane panel as context for whether some other lane is also under pressure.*\n\n###### Healthy range:\n*Both near zero when synced. During catch-up, deferrals non-zero is expected, but timeouts should track rather than flatten.*\n\n###### Watch for:\n*A ledger deferral rate that climbs while the ledger timeout rate stays pinned. Compare against the all-lane panel: if the all-lane pair diverges but these two do not, the stall is in another acquisition lane and ledger acquisition is not the problem. Cross-check Acquisition Progress -- a flat completion rate alongside a divergence here confirms the acquisitions are stuck rather than merely slow.*\n\n###### Keywords:\n- **NodeStore** *(per node)* \u2014 the key-value object store holding ledger data (tree nodes), backed by NuDB.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[app/ledger/detail/TimeoutCounter.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/detail/TimeoutCounter.cpp)\n\n###### Function:\n`TimeoutCounter::queueJob, TimeoutCounter::invokeOnTimer (via isLedgerAcquisition) / AcquireStats`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#nodestore)", "type": "timeseries", "gridPos": { "h": 8, diff --git a/docker/telemetry/grafana/dashboards/ledger-operations.json b/docker/telemetry/grafana/dashboards/ledger-operations.json index 96b2d40c92..d3c6ae2342 100644 --- a/docker/telemetry/grafana/dashboards/ledger-operations.json +++ b/docker/telemetry/grafana/dashboards/ledger-operations.json @@ -1,6 +1,36 @@ { "annotations": { - "list": [] + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + }, + { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": false, + "iconColor": "rgb(70, 70, 70)", + "name": "Annotate perf-iac runs", + "target": { + "limit": 100, + "matchAny": false, + "tags": ["perf-iac"], + "type": "tags" + }, + "type": "tags" + } + ] }, "editable": true, "fiscalYearStartMonth": 0, @@ -10,7 +40,7 @@ "panels": [ { "title": "Ledger Build Rate", - "description": "###### What this is:\n*How many new ledgers this node finishes building per second.*\n\n###### How it's computed:\n*Per-second rate of completed ledger-build operations, averaged over 5 minutes and split by node.*\n\n###### Reading it:\n*A steady flat line; the value should track the network close cadence.*\n\n###### Healthy range:\n*About 0.2-0.3 ledgers/sec on mainnet (roughly one every 3-5s); workload-dependent on test networks.*\n\n###### Watch for:\n*A drop toward zero (node fell out of sync or stalled) or a value well above the network rate (rebuilding history).*\n\n###### Source:\n[BuildLedger.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/detail/BuildLedger.cpp)\n\n###### Function:\n`buildLedgerImpl`", + "description": "###### What this is:\n*How many new ledgers this node finishes building per second.*\n\n###### How it's computed:\n*Per-second rate of completed ledger-build operations, averaged over 5 minutes and split by node.*\n\n###### Reading it:\n*A steady flat line; the value should track the network close cadence.*\n\n###### Healthy range:\n*About 0.2-0.3 ledgers/sec on mainnet (roughly one every 3-5s); workload-dependent on test networks.*\n\n###### Watch for:\n*A drop toward zero (node fell out of sync or stalled) or a value well above the network rate (rebuilding history).*\n\n###### Keywords:\n- **Ledger build** *(per node)* \u2014 constructing the new ledger by applying the agreed transaction set to the prior ledger.\n- **Ledger close interval** *(network-wide)* \u2014 the network's steady ledger rhythm \u2014 roughly one closed ledger every 3-5 seconds on Mainnet.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[BuildLedger.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/detail/BuildLedger.cpp)\n\n###### Function:\n`buildLedgerImpl`\n\n###### References:\n[Ledger build](https://xrpl.org/docs/concepts/ledgers/open-closed-validated-ledgers) \u00b7 [Ledger close interval](https://xrpl.org/docs/concepts/ledgers/ledger-close-times) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#ledger-build)", "type": "stat", "gridPos": { "h": 8, @@ -28,14 +58,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"ledger.build\"}[$__rate_interval])), \"series\", \"Builds / Sec\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"ledger.build\"}[$__rate_interval])), \"series\", \"Builds / Sec\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: ledgers/s" }, "overrides": [] @@ -43,7 +74,7 @@ }, { "title": "Ledger Build Duration", - "description": "###### What this is:\n*The time taken to build a single ledger, at the 95th percentile.*\n\n###### How it's computed:\n*95th-percentile of ledger-build durations over a 5-minute window, per node.*\n\n###### Reading it:\n*Lower is better; the line should stay well under the ledger interval.*\n\n###### Healthy range:\n*Typically tens to low hundreds of milliseconds; workload-dependent.*\n\n###### Watch for:\n*Sustained rises approaching the close interval, which indicate heavy transaction sets or disk/I/O pressure.*\n\n###### Source:\n[BuildLedger.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/detail/BuildLedger.cpp)\n\n###### Function:\n`buildLedgerImpl`", + "description": "###### What this is:\n*The time taken to build a single ledger, at the 95th percentile.*\n\n###### How it's computed:\n*95th-percentile of ledger-build durations over a 5-minute window, per node.*\n\n###### Reading it:\n*Lower is better; the line should stay well under the ledger interval.*\n\n###### Healthy range:\n*Typically tens to low hundreds of milliseconds; workload-dependent.*\n\n###### Watch for:\n*Sustained rises approaching the close interval, which indicate heavy transaction sets or disk/I/O pressure.*\n\n###### Keywords:\n- **Ledger build** *(per node)* \u2014 constructing the new ledger by applying the agreed transaction set to the prior ledger.\n- **Ledger close interval** *(network-wide)* \u2014 the network's steady ledger rhythm \u2014 roughly one closed ledger every 3-5 seconds on Mainnet.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[BuildLedger.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/detail/BuildLedger.cpp)\n\n###### Function:\n`buildLedgerImpl`\n\n###### References:\n[Ledger build](https://xrpl.org/docs/concepts/ledgers/open-closed-validated-ledgers) \u00b7 [Ledger close interval](https://xrpl.org/docs/concepts/ledgers/ledger-close-times) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#ledger-build)", "type": "timeseries", "gridPos": { "h": 8, @@ -61,14 +92,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(histogram_quantile(0.95, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_duration_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"ledger.build\"}[5m]))), \"series\", \"P95 Build Duration\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(histogram_quantile(0.95, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_duration_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"ledger.build\"}[5m]))), \"series\", \"P95 Build Duration\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "ms", "custom": { "axisLabel": "Duration (ms)", @@ -83,7 +115,7 @@ }, { "title": "Ledger Validation Rate", - "description": "###### What this is:\n*How often ledgers reach full validation (accepted by the trusted validator quorum) per second.*\n\n###### How it's computed:\n*Per-second rate of ledger-validation events over 5 minutes, per node.*\n\n###### Reading it:\n*Should closely match the build rate under normal, in-sync operation.*\n\n###### Healthy range:\n*About 0.2-0.3/sec on mainnet; workload-dependent elsewhere.*\n\n###### Watch for:\n*A validation rate that lags the build rate, signalling the node is building ahead of the network consensus it trusts.*\n\n###### Source:\n[LedgerMaster.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/detail/LedgerMaster.cpp)\n\n###### Function:\n`LedgerMaster::checkAccept`", + "description": "###### What this is:\n*How often ledgers reach full validation (accepted by the trusted validator quorum) per second.*\n\n###### How it's computed:\n*Per-second rate of ledger-validation events over 5 minutes, per node.*\n\n###### Reading it:\n*Should closely match the build rate under normal, in-sync operation.*\n\n###### Healthy range:\n*About 0.2-0.3/sec on mainnet; workload-dependent elsewhere.*\n\n###### Watch for:\n*A validation rate that lags the build rate, signalling the node is building ahead of the network consensus it trusts.*\n\n###### Keywords:\n- **Ledger validation** *(network event)* \u2014 the second consensus stage where the node confirms a built ledger matches the trusted validator quorum and marks it final.\n- **Validation quorum** *(network-wide)* \u2014 the minimum number of agreeing trusted validations needed to declare a ledger fully validated.\n- **Ledger build** *(per node)* \u2014 constructing the new ledger by applying the agreed transaction set to the prior ledger.\n- **Consensus** *(network event)* \u2014 the protocol by which validators agree on the next ledger's transaction set and close time.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[LedgerMaster.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/detail/LedgerMaster.cpp)\n\n###### Function:\n`LedgerMaster::checkAccept`\n\n###### References:\n[Ledger validation](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure#validation) \u00b7 [Validation quorum](https://xrpl.org/docs/concepts/consensus-protocol/negative-unl) \u00b7 [Ledger build](https://xrpl.org/docs/concepts/ledgers/open-closed-validated-ledgers) \u00b7 [Consensus](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#ledger-validation)", "type": "stat", "gridPos": { "h": 8, @@ -101,14 +133,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"ledger.validate\"}[$__rate_interval])), \"series\", \"Validations / Sec\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"ledger.validate\"}[$__rate_interval])), \"series\", \"Validations / Sec\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: ledgers/s" }, "overrides": [] @@ -116,7 +149,7 @@ }, { "title": "Ledger Build Duration Heatmap", - "description": "###### What this is:\n*The full distribution of ledger-build times over the window, not just a single percentile.*\n\n###### How it's computed:\n*Counts of ledger builds falling in each duration band per 5-minute window, shown as color density.*\n\n###### Reading it:\n*A tight low band is healthy; scattered high cells mean occasional slow builds.*\n\n###### Healthy range:\n*Most mass concentrated in the low-millisecond bands; workload-dependent.*\n\n###### Watch for:\n*A second cluster of hot cells at high durations (bimodal build times) hidden by percentile charts.*\n\n###### Source:\n[BuildLedger.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/detail/BuildLedger.cpp)\n\n###### Function:\n`buildLedgerImpl`", + "description": "###### What this is:\n*The full distribution of ledger-build times over the window, not just a single percentile.*\n\n###### How it's computed:\n*Counts of ledger builds falling in each duration band per 5-minute window, shown as color density.*\n\n###### Reading it:\n*A tight low band is healthy; scattered high cells mean occasional slow builds.*\n\n###### Healthy range:\n*Most mass concentrated in the low-millisecond bands; workload-dependent.*\n\n###### Watch for:\n*A second cluster of hot cells at high durations (bimodal build times) hidden by percentile charts.*\n\n###### Keywords:\n- **Ledger build** *(per node)* \u2014 constructing the new ledger by applying the agreed transaction set to the prior ledger.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[BuildLedger.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/detail/BuildLedger.cpp)\n\n###### Function:\n`buildLedgerImpl`\n\n###### References:\n[Ledger build](https://xrpl.org/docs/concepts/ledgers/open-closed-validated-ledgers) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#ledger-build)", "type": "heatmap", "gridPos": { "h": 8, @@ -137,7 +170,8 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, "expr": "sum(increase(span_duration_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"ledger.build\"}[5m])) by (le)", "legendFormat": "{{le}}", @@ -153,7 +187,7 @@ }, { "title": "Transaction Apply Duration", - "description": "###### What this is:\n*The time spent applying the agreed transaction set into the new ledger, at the 95th percentile.*\n\n###### How it's computed:\n*95th-percentile of transaction-apply durations over 5 minutes, per node.*\n\n###### Reading it:\n*Lower is better; this is a large share of total build time.*\n\n###### Healthy range:\n*A few to tens of milliseconds; scales with transaction volume per ledger.*\n\n###### Watch for:\n*Spikes during large or expensive transaction sets, which push out overall ledger build time.*\n\n###### Source:\n[BuildLedger.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/detail/BuildLedger.cpp)\n\n###### Function:\n`applyTransactions`", + "description": "###### What this is:\n*The time spent applying the agreed transaction set into the new ledger, at the 95th percentile.*\n\n###### How it's computed:\n*95th-percentile of transaction-apply durations over 5 minutes, per node.*\n\n###### Reading it:\n*Lower is better; this is a large share of total build time.*\n\n###### Healthy range:\n*A few to tens of milliseconds; scales with transaction volume per ledger.*\n\n###### Watch for:\n*Spikes during large or expensive transaction sets, which push out overall ledger build time.*\n\n###### Keywords:\n- **Transaction apply phase** *(per node)* \u2014 the step that executes the agreed transaction set into the new ledger during a close.\n- **Ledger build** *(per node)* \u2014 constructing the new ledger by applying the agreed transaction set to the prior ledger.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[BuildLedger.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/detail/BuildLedger.cpp)\n\n###### Function:\n`applyTransactions`\n\n###### References:\n[Transaction apply phase](https://xrpl.org/docs/concepts/ledgers/open-closed-validated-ledgers) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#transaction-apply-phase)", "type": "timeseries", "gridPos": { "h": 8, @@ -171,14 +205,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(histogram_quantile(0.95, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_duration_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"tx.apply\"}[5m]))), \"series\", \"P95 tx.apply\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(histogram_quantile(0.95, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_duration_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"tx.apply\"}[5m]))), \"series\", \"P95 tx.apply\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "ms", "custom": { "axisLabel": "Duration (ms)", @@ -193,7 +228,7 @@ }, { "title": "Transaction Apply Rate", - "description": "###### What this is:\n*How often the transaction-apply phase runs per second (once per ledger build).*\n\n###### How it's computed:\n*Per-second rate of transaction-apply operations over 5 minutes, per node.*\n\n###### Reading it:\n*Should track the ledger build rate almost exactly.*\n\n###### Healthy range:\n*About 0.2-0.3/sec on mainnet; workload-dependent.*\n\n###### Watch for:\n*Divergence from the build rate, which would indicate a metric or pipeline anomaly.*\n\n###### Source:\n[BuildLedger.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/detail/BuildLedger.cpp)\n\n###### Function:\n`applyTransactions`", + "description": "###### What this is:\n*How often the transaction-apply phase runs per second (once per ledger build).*\n\n###### How it's computed:\n*Per-second rate of transaction-apply operations over 5 minutes, per node.*\n\n###### Reading it:\n*Should track the ledger build rate almost exactly.*\n\n###### Healthy range:\n*About 0.2-0.3/sec on mainnet; workload-dependent.*\n\n###### Watch for:\n*Divergence from the build rate, which would indicate a metric or pipeline anomaly.*\n\n###### Keywords:\n- **Transaction apply phase** *(per node)* \u2014 the step that executes the agreed transaction set into the new ledger during a close.\n- **Ledger build** *(per node)* \u2014 constructing the new ledger by applying the agreed transaction set to the prior ledger.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[BuildLedger.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/detail/BuildLedger.cpp)\n\n###### Function:\n`applyTransactions`\n\n###### References:\n[Transaction apply phase](https://xrpl.org/docs/concepts/ledgers/open-closed-validated-ledgers) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#transaction-apply-phase)", "type": "timeseries", "gridPos": { "h": 8, @@ -211,14 +246,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"tx.apply\"}[$__rate_interval])), \"series\", \"tx.apply / Sec\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"tx.apply\"}[$__rate_interval])), \"series\", \"tx.apply / Sec\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: transactions/s", "custom": { "axisLabel": "Transactions / Sec", @@ -233,7 +269,7 @@ }, { "title": "Ledger Store Rate", - "description": "###### What this is:\n*How often completed ledgers are written into ledger history per second.*\n\n###### How it's computed:\n*Per-second rate of ledger-store operations over 5 minutes, per node.*\n\n###### Reading it:\n*Should match the build rate during normal operation.*\n\n###### Healthy range:\n*About 0.2-0.3/sec on mainnet; can burst higher while backfilling history.*\n\n###### Watch for:\n*A store rate below the build rate (storage falling behind) or a stall at zero.*\n\n###### Source:\n[LedgerMaster.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/detail/LedgerMaster.cpp)\n\n###### Function:\n`LedgerMaster::storeLedger`", + "description": "###### What this is:\n*How often completed ledgers are written into ledger history per second.*\n\n###### How it's computed:\n*Per-second rate of ledger-store operations over 5 minutes, per node.*\n\n###### Reading it:\n*Should match the build rate during normal operation.*\n\n###### Healthy range:\n*About 0.2-0.3/sec on mainnet; can burst higher while backfilling history.*\n\n###### Watch for:\n*A store rate below the build rate (storage falling behind) or a stall at zero.*\n\n###### Keywords:\n- **Ledger store** *(per node)* \u2014 writing a completed ledger into the node's ledger history on disk.\n- **Ledger build** *(per node)* \u2014 constructing the new ledger by applying the agreed transaction set to the prior ledger.\n- **Back-fill / catch-up** *(per node)* \u2014 fetching missing historical ledgers from peers to fill gaps or reach the network tip.\n- **Consensus stall** *(per node)* \u2014 a health check reporting that consensus is not making forward progress.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[LedgerMaster.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/detail/LedgerMaster.cpp)\n\n###### Function:\n`LedgerMaster::storeLedger`\n\n###### References:\n[Ledger build](https://xrpl.org/docs/concepts/ledgers/open-closed-validated-ledgers) \u00b7 [Consensus stall](https://xrpl.org/docs/concepts/consensus-protocol/consensus-principles-and-rules) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#ledger-store)", "type": "stat", "gridPos": { "h": 8, @@ -251,14 +287,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"ledger.store\"}[$__rate_interval])), \"series\", \"Stores / Sec\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"ledger.store\"}[$__rate_interval])), \"series\", \"Stores / Sec\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: ledgers/s" }, "overrides": [] @@ -266,7 +303,7 @@ }, { "title": "Build vs Close Duration", - "description": "###### What this is:\n*Ledger build time compared with the total consensus ledger-close time, both at the 95th percentile.*\n\n###### How it's computed:\n*Two 95th-percentile duration series over 5 minutes: ledger construction and the full consensus close, per node.*\n\n###### Reading it:\n*Build should sit below close; the gap is consensus overhead outside construction.*\n\n###### Healthy range:\n*Close a bit above build; both workload-dependent and under the round interval.*\n\n###### Watch for:\n*A widening gap (consensus-pipeline overhead growing) or build time approaching close time.*\n\n###### Source:\n[BuildLedger.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/detail/BuildLedger.cpp) \u00b7 [RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`buildLedgerImpl ; RCLConsensus::Adaptor::onClose`", + "description": "###### What this is:\n*Ledger build time compared with the full consensus round duration, both at the 95th percentile.*\n\n###### How it's computed:\n*Two 95th-percentile duration series over 5 minutes: ledger construction (ledger.build span) and the whole consensus round from open to accept (consensus.round span), per node.*\n\n###### Reading it:\n*Build should sit well below the round; the gap is consensus wait time (proposing, converging, validating) outside construction.*\n\n###### Healthy range:\n*Round tracks the network close interval (~3-5s on mainnet); build is a fraction of it (tens to hundreds of ms).*\n\n###### Watch for:\n*Build time approaching the round duration \u2014 construction is dominating the close and leaving little slack.*\n\n###### Note:\n*The close series uses consensus.round, not consensus.ledger_close: the latter span only wraps the onClose() prologue (sub-millisecond) and is not the ledger close time.*\n\n###### Keywords:\n- **Consensus round** *(network event)* \u2014 one propose-and-revise iteration of consensus; several may run before validators converge on a ledger.\n- **Ledger build** *(per node)* \u2014 constructing the new ledger by applying the agreed transaction set to the prior ledger.\n- **Consensus** *(network event)* \u2014 the protocol by which validators agree on the next ledger's transaction set and close time.\n- **Consensus mode** *(per node)* \u2014 the node's role/health in the current round: Proposing, Observing, Wrong Ledger, or Switched Ledger.\n- **Ledger close interval** *(network-wide)* \u2014 the network's steady ledger rhythm \u2014 roughly one closed ledger every 3-5 seconds on Mainnet.\n- **Ledger close** *(network event)* \u2014 the current open ledger is closed and a new closed ledger is built from the agreed transaction set.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[BuildLedger.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/detail/BuildLedger.cpp) \u00b7 [RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`buildLedgerImpl ; RCLConsensus::Adaptor::onClose (round span)`\n\n###### References:\n[Consensus round](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) \u00b7 [Ledger build](https://xrpl.org/docs/concepts/ledgers/open-closed-validated-ledgers) \u00b7 [Ledger close interval](https://xrpl.org/docs/concepts/ledgers/ledger-close-times) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#consensus-round)", "type": "timeseries", "gridPos": { "h": 8, @@ -284,20 +321,22 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(histogram_quantile(0.95, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_duration_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"ledger.build\"}[5m]))), \"series\", \"P95 ledger.build\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(histogram_quantile(0.95, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_duration_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"ledger.build\"}[5m]))), \"series\", \"P95 ledger.build\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(histogram_quantile(0.95, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_duration_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"consensus.ledger_close\"}[5m]))), \"series\", \"P95 consensus.ledger_close\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(histogram_quantile(0.95, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_duration_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"consensus.round\"}[5m]))), \"series\", \"P95 Close (consensus.round)\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "ms", "custom": { "axisLabel": "Duration (ms)", @@ -309,12 +348,110 @@ }, "overrides": [] } + }, + { + "title": "Ledger Close Interval & Age", + "description": "###### What this is:\n*The wall-clock time between consecutive ledger closes \u2014 the network close cadence.*\n\n###### How it's computed:\n*Close Interval: 1 / rate(ledgers_closed_total), the average seconds between closes from the monotonic close counter (scrape-independent, unlike a gauge delta which would alias to the scrape period). Last-Close Age: time() minus the last-close network time (server_info last_close_time gauge + Ripple-epoch offset), i.e. seconds since the last ledger closed.*\n\n###### Reading it:\n*A steady line near the network's target close interval.*\n\n###### Healthy range:\n*About 3-5s on mainnet; workload-dependent on test networks.*\n\n###### Watch for:\n*A rising interval (consensus slowing or the node lagging) or a flat line at zero (ledgers no longer closing).*\n\n###### Keywords:\n- **Ledger close** *(network event)* \u2014 the current open ledger is closed and a new closed ledger is built from the agreed transaction set.\n- **Ledger close interval** *(network-wide)* \u2014 the network's steady ledger rhythm \u2014 roughly one closed ledger every 3-5 seconds on Mainnet.\n- **Consensus** *(network event)* \u2014 the protocol by which validators agree on the next ledger's transaction set and close time.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp) \u00b7 [RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`registerServerInfoGauge (last_close_time) ; ledgers_closed_total`\n\n###### References:\n[Ledger close](https://xrpl.org/docs/concepts/ledgers/open-closed-validated-ledgers) \u00b7 [Ledger close interval](https://xrpl.org/docs/concepts/ledgers/ledger-close-times) \u00b7 [Consensus](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#ledger-close)", + "type": "timeseries", + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 32 + }, + "options": { + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(time() - (server_info{metric=\"last_close_time\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"} + 946684800), \"series\", \"Last-Close Age (s)\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(1 / sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(ledgers_closed_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])), \"series\", \"Close Interval\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" + } + ], + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "unit": "s", + "custom": { + "axisLabel": "Seconds", + "spanNulls": 1800000, + "insertNulls": false, + "showPoints": "auto", + "pointSize": 3 + } + }, + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": "/^Last-Close Age/" + }, + "properties": [ + { + "id": "unit", + "value": "s" + }, + { + "id": "custom.axisLabel", + "value": "Age Since Last Close (Sec)" + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": "/^Close Interval/" + }, + "properties": [ + { + "id": "unit", + "value": "s" + }, + { + "id": "custom.axisPlacement", + "value": "right" + }, + { + "id": "custom.axisLabel", + "value": "Close Interval (Sec)" + } + ] + } + ] + } } ], "schemaVersion": 39, "tags": ["ledger"], "templating": { "list": [ + { + "name": "DS_PROMETHEUS", + "type": "datasource", + "label": "Prometheus", + "query": "prometheus", + "regex": "", + "current": {}, + "hide": 0, + "refresh": 1, + "includeAll": false, + "multi": false, + "options": [] + }, { "name": "service_name", "label": "Service Name", @@ -323,7 +460,7 @@ "query": "label_values(service_name)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -343,7 +480,7 @@ "query": "label_values(deployment_environment)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -363,7 +500,7 @@ "query": "label_values(xrpl_network_type)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -443,7 +580,7 @@ "query": "label_values(target_info, service_instance_id)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -463,6 +600,6 @@ }, "title": "Ledger Operations", "uid": "ledger-operations", - "refresh": "5s", - "description": "What this shows: Ledger construction, validation, and storage activity and timing for this node.\nUse it to: Confirm ledgers are being built, validated, and stored on schedule and find the slow stage when they are not." + "refresh": "30s", + "description": "What this shows: Ledger construction, validation, and storage activity and timing for this node. \u2014 Use it to: Confirm ledgers are being built, validated, and stored on schedule and find the slow stage when they are not." } diff --git a/docker/telemetry/grafana/dashboards/log-derived-insights.json b/docker/telemetry/grafana/dashboards/log-derived-insights.json new file mode 100644 index 0000000000..87028f1ec0 --- /dev/null +++ b/docker/telemetry/grafana/dashboards/log-derived-insights.json @@ -0,0 +1,2471 @@ +{ + "description": "What this shows: Signals derived from xrpld's debug.log via Loki (LogQL), covering detail that no metric or span records — manifest dispositions per master key, resource fee charges per IP and public key, ledger-fetch duplicate ratios, peer disconnect reasons, and consensus phase transitions. — Use it to: Investigate behaviour that the Prometheus dashboards cannot express, and to attribute load or abuse to a specific peer, key, or job. — REQUIRES DEBUG LOGS: most panels here read from log lines emitted at DBG severity, which xrpld suppresses by default (default threshold is Info, see Main.cpp). On a default-configured node those panels are EMPTY, and an empty panel here means 'not collecting', NOT 'no problem'. Enable per partition with `log_level debug` — for example `log_level ManifestCache debug`. Rows are marked [DBG] when they require debug logs and [DEFAULT OK] when they work at the default Info level.", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "links": [], + "schemaVersion": 39, + "tags": ["node", "logs", "logql"], + "time": { + "from": "now-1h", + "to": "now" + }, + "title": "Log-Derived Insights", + "uid": "log-derived-insights", + "refresh": "30s", + "annotations": { + "list": [] + }, + "templating": { + "list": [ + { + "name": "DS_LOKI", + "label": "Loki Data Source", + "description": "Loki data source holding xrpld debug.log lines", + "type": "datasource", + "query": "loki", + "current": { + "text": "Loki", + "value": "loki" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "refresh": 1, + "options": [], + "skipUrlSync": false + }, + { + "name": "service_name", + "label": "Service Name", + "description": "Filter by emitting service", + "type": "query", + "query": "label_values(service_name)", + "datasource": { + "type": "loki", + "uid": "loki" + }, + "includeAll": true, + "allValue": ".*", + "current": { + "text": "xrpld", + "value": "xrpld" + }, + "multi": true, + "refresh": 2, + "sort": 1 + }, + { + "name": "deployment_environment", + "label": "Deployment Environment", + "description": "Filter by deployment tier (local, ci, test, prod)", + "type": "query", + "query": "label_values({service_name=\"xrpld\"}, deployment_environment)", + "datasource": { + "type": "loki", + "uid": "loki" + }, + "includeAll": true, + "allValue": ".*", + "current": { + "text": "All", + "value": "$__all" + }, + "multi": true, + "refresh": 2, + "sort": 1 + }, + { + "name": "node", + "label": "Node", + "description": "Filter by rippled node (service.instance.id)", + "type": "query", + "query": "label_values({service_name=\"xrpld\"}, service_instance_id)", + "datasource": { + "type": "loki", + "uid": "loki" + }, + "includeAll": true, + "allValue": ".*", + "current": { + "text": "All", + "value": "$__all" + }, + "multi": true, + "refresh": 2, + "sort": 1 + }, + { + "name": "xrpl_network_type", + "label": "Network Type", + "description": "Filter by XRPL network. Structured metadata, so values are enumerated rather than discovered.", + "type": "custom", + "query": "mainnet,testnet,devnet", + "includeAll": true, + "allValue": ".*", + "current": { + "text": "All", + "value": "$__all" + }, + "multi": true, + "options": [], + "refresh": 0, + "sort": 0 + }, + { + "name": "severity", + "label": "Log Severity", + "description": "Filter by xrpld log severity (DBG, NFO, WRN, ERR, FTL). Structured metadata, not a stream label.", + "type": "custom", + "query": "DBG,NFO,WRN,ERR,FTL", + "includeAll": true, + "allValue": ".*", + "current": { + "text": "All", + "value": "$__all" + }, + "multi": true, + "options": [], + "refresh": 0, + "sort": 0 + }, + { + "name": "consensus_phase", + "label": "Consensus Phase", + "description": "Derived from log text: ConsensusPhase transitions (Open, Establish, Accepted)", + "type": "custom", + "query": "Open,Establish,Accepted", + "includeAll": true, + "allValue": ".*", + "current": { + "text": "All", + "value": "$__all" + }, + "multi": true, + "options": [], + "refresh": 0, + "sort": 0 + }, + { + "name": "consensus_mode", + "label": "Consensus Mode", + "description": "Derived from log text: ConsensusLogger operating mode (full, syncing, observing, tracking)", + "type": "custom", + "query": "full,syncing,observing,tracking", + "includeAll": true, + "allValue": ".*", + "current": { + "text": "All", + "value": "$__all" + }, + "multi": true, + "options": [], + "refresh": 0, + "sort": 0 + }, + { + "name": "manifest_action", + "label": "Manifest Action", + "description": "Derived from log text: ManifestCache disposition. UntrustedCapacity exists only on rc5+ builds.", + "type": "custom", + "query": "AcceptedNew,AcceptedUpdate,Stale,Revoked,Invalid,UntrustedCapacity", + "includeAll": true, + "allValue": ".*", + "current": { + "text": "All", + "value": "$__all" + }, + "multi": true, + "options": [], + "refresh": 0, + "sort": 0 + }, + { + "name": "charge_reason", + "label": "Fee Charge Reason", + "description": "Derived from log text: Resource fee charge reason", + "type": "custom", + "query": "useless data,moderate peer request,heavy peer request,light peer request,trivial peer request,unwanted data,init drop", + "includeAll": true, + "allValue": ".*", + "current": { + "text": "All", + "value": "$__all" + }, + "multi": true, + "options": [], + "refresh": 0, + "sort": 0 + }, + { + "name": "topn", + "label": "Top N", + "description": "Row limit for top-N tables. Loki caps a query at 2000 series, so unbounded per-key aggregation fails.", + "type": "custom", + "query": "5,10,15,20,25", + "includeAll": false, + "current": { + "text": "10", + "value": "10" + }, + "multi": false, + "options": [], + "refresh": 0, + "sort": 0 + } + ] + }, + "panels": [ + { + "type": "text", + "title": "Read This First — Debug Log Requirement", + "gridPos": { + "h": 12, + "w": 24, + "x": 0, + "y": 0 + }, + "options": { + "mode": "markdown", + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "### ⚠️ Most panels on this dashboard require debug logs\n\nxrpld's default log threshold is **Info** (`Severity thresh = Severity::Info` in `Main.cpp`). Every panel in a row marked **[DBG]** reads log lines emitted at `DBG` severity, which a default-configured node **does not write**.\n\n**An empty [DBG] panel means \"not collecting\", NOT \"no problem\".** A manifest dashboard reading zero because `ManifestCache` sits at Info is indistinguishable from a healthy one.\n\nEnable per partition rather than globally — global debug is a firehose (`Resource` alone emits ~329k lines / 6h):\n\n```\nlog_level ManifestCache debug\nlog_level Resource debug\nlog_level InboundLedger debug\nlog_level Peer debug\nlog_level LedgerConsensus debug\n```\n\nRows marked **[DEFAULT OK]** work at the default Info level and need no configuration change." + }, + "id": 1 + }, + { + "type": "row", + "title": "Worst Offenders — Node Ranking [MIXED]", + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 12 + }, + "panels": [], + "id": 2 + }, + { + "type": "stat", + "title": "Nodes By Error Volume", + "description": "###### What this is:\n*Which nodes are logging the most errors.*\n\n###### How it's computed:\n*Count of ERR and FTL log lines per node over the dashboard window, ranked highest first.*\n\n###### Reading it:\n*The top entry is the node in the most trouble. Compare nodes rather than reading an absolute value.*\n\n###### Healthy range:\n*Zero, or a small flat count. ERR is not routine.*\n\n###### Watch for:\n*Any node pulling far ahead of its peers, which usually means a fault local to that node rather than a network condition.*\n\n###### Keywords:\n- **ERR / FTL** *(per line)* — the two most severe xrpld log levels; both survive the default Info threshold.\n\n###### Computation boundary:\n*Result: Per node — a count over the dashboard window, ranked worst-first.*\n*NOT recorded as a metric. Derived in the Grafana query by regex over raw log text.*\n\n###### Source:\n[Log.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/basics/Log.cpp)\n\n###### Function:\n`Logs::Sink::write`\n\n###### References:\n[Loki log queries](https://grafana.com/docs/loki/latest/query/log_queries/)", + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 0, + "y": 13 + }, + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.service_instance_id}", + "unit": "suffix: errors", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 10 + }, + { + "color": "red", + "value": 100 + } + ] + }, + "custom": {} + }, + "overrides": [] + }, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "textMode": "value_and_name", + "wideLayout": true, + "reduceOptions": { + "calcs": ["max"], + "fields": "", + "values": false + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "refId": "A", + "expr": "topk($topn, sum by (service_instance_id) (count_over_time({service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | severity =~ `ERR|FTL` [$__range])))", + "instant": true, + "queryType": "instant" + } + ], + "id": 3 + }, + { + "type": "stat", + "title": "Nodes By Attack-Like Input", + "description": "###### What this is:\n*Which nodes are receiving the most malformed or unwanted peer payloads.*\n\n###### How it's computed:\n*Count of Resource fee charges whose reason is useless data, unwanted data, or init drop, per node.*\n\n###### Reading it:\n*These three reasons indicate a peer sent something the node could not use, so a high count is the closest log-derived proxy for abusive input.*\n\n###### Healthy range:\n*Low and flat. Some useless data is normal on a busy overlay.*\n\n###### Watch for:\n*A sharp rise on one node, especially paired with a single dominant IP in the charged-peers table below.*\n\n###### Keywords:\n- **Useless data** *(per charge)* — payload the node could not use, e.g. an empty or malformed message.\n- **Init drop** *(per charge)* — a connection dropped during initialisation.\n\n###### Computation boundary:\n*Result: Per node — a count over the dashboard window, ranked worst-first.*\n*NOT recorded as a metric. Derived in the Grafana query by regex over raw log text.*\n\n###### Source:\n[ResourceManager.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/resource/detail/ResourceManager.cpp)\n\n###### Function:\n`Logic::charge`\n\n###### Note:\n*Requires debug logs on the Resource partition.*\n\n###### References:\n[Peer protocol](https://xrpl.org/docs/concepts/networks-and-servers/peer-protocol)", + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 6, + "y": 13 + }, + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.service_instance_id}", + "unit": "suffix: charges", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 100 + }, + { + "color": "red", + "value": 1000 + } + ] + }, + "custom": {} + }, + "overrides": [] + }, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "textMode": "value_and_name", + "wideLayout": true, + "reduceOptions": { + "calcs": ["max"], + "fields": "", + "values": false + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "refId": "A", + "expr": "topk($topn, sum by (service_instance_id) (count_over_time({service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | partition = `Resource` |~ `useless data|unwanted data|init drop` [$__range])))", + "instant": true, + "queryType": "instant" + } + ], + "id": 4 + }, + { + "type": "stat", + "title": "Nodes By Total Fee Charged", + "description": "###### What this is:\n*Which nodes are absorbing the most peer-imposed load, weighted by fee amount.*\n\n###### How it's computed:\n*Sum of every resource fee amount parsed from the Resource partition, per node.*\n\n###### Reading it:\n*Weighted by cost rather than event count, so one heavy request at 2000 outranks eight moderate ones at 250.*\n\n###### Healthy range:\n*Proportional to peer count and request volume; compare nodes of similar role.*\n\n###### Watch for:\n*One node far above peers of the same role, which means it is carrying disproportionate peer load.*\n\n###### Keywords:\n- **Fee weight** *(per charge)* — the credit amount: 2000 heavy, 250 moderate, 150 useless data.\n\n###### Computation boundary:\n*Result: Per node — a count over the dashboard window, ranked worst-first.*\n*NOT recorded as a metric. Derived in the Grafana query by regex over raw log text.*\n\n###### Source:\n[ResourceManager.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/resource/detail/ResourceManager.cpp)\n\n###### Function:\n`Logic::charge`\n\n###### Note:\n*Requires debug logs on the Resource partition.*\n\n###### References:\n[Peer protocol](https://xrpl.org/docs/concepts/networks-and-servers/peer-protocol)", + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 12, + "y": 13 + }, + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.service_instance_id}", + "unit": "suffix: fee", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 1000000 + }, + { + "color": "red", + "value": 10000000 + } + ] + }, + "custom": {} + }, + "overrides": [] + }, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "textMode": "value_and_name", + "wideLayout": true, + "reduceOptions": { + "calcs": ["max"], + "fields": "", + "values": false + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "refId": "A", + "expr": "topk($topn, sum by (service_instance_id) (sum_over_time({service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | partition = `Resource` | regexp `\\(\\$(?P[0-9]+)\\)` | unwrap fee [$__range])))", + "instant": true, + "queryType": "instant" + } + ], + "id": 5 + }, + { + "type": "stat", + "title": "Nodes By Manifest Rejection", + "description": "###### What this is:\n*Which nodes are rejecting the most inbound manifests.*\n\n###### How it's computed:\n*Count of ManifestCache lines with a Stale, Invalid, Revoked, or UntrustedCapacity outcome, per node.*\n\n###### Reading it:\n*A manifest flood shows up here first. Stale dominates normally because peers re-gossip manifests the node already holds.*\n\n###### Healthy range:\n*Workload-dependent; nodes on the same network should sit close together.*\n\n###### Watch for:\n*A single node far ahead, or any Invalid at all, which means a signature failed verification.*\n\n###### Keywords:\n- **Rejection** *(per manifest)* — any non-accepted disposition.\n- **UntrustedCapacity** *(per manifest)* — rate-limit rejection for an unlisted key; rc5+ builds only.\n\n###### Computation boundary:\n*Result: Per node — a count over the dashboard window, ranked worst-first.*\n*NOT recorded as a metric. Derived in the Grafana query by regex over raw log text.*\n\n###### Source:\n[Manifest.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/server/Manifest.cpp)\n\n###### Function:\n`ManifestCache::applyManifest`\n\n###### Note:\n*Requires debug logs on the ManifestCache partition.*\n\n###### References:\n[Validator keys](https://xrpl.org/docs/concepts/consensus-protocol/validator-keys)", + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 18, + "y": 13 + }, + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.service_instance_id}", + "unit": "suffix: rejections", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 1000 + }, + { + "color": "red", + "value": 10000 + } + ] + }, + "custom": {} + }, + "overrides": [] + }, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "textMode": "value_and_name", + "wideLayout": true, + "reduceOptions": { + "calcs": ["max"], + "fields": "", + "values": false + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "refId": "A", + "expr": "topk($topn, sum by (service_instance_id) (count_over_time({service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | partition = `ManifestCache` |~ `Manifest: (Stale|Invalid|Revoked|UntrustedCapacity)` [$__range])))", + "instant": true, + "queryType": "instant" + } + ], + "id": 6 + }, + { + "type": "stat", + "title": "Nodes By Consensus Problem", + "description": "###### What this is:\n*Which nodes are logging the most consensus warnings and errors.*\n\n###### How it's computed:\n*Count of LedgerConsensus lines at WRN severity or above, per node.*\n\n###### Reading it:\n*Consensus is a network-wide process, so a single node standing out points at that node rather than the network.*\n\n###### Healthy range:\n*Low. Some warnings occur during normal round churn.*\n\n###### Watch for:\n*One node far above its peers, or a step change after a deploy.*\n\n###### Keywords:\n- **Consensus round** *(per ledger)* — one Open to Establish to Accepted cycle.\n- **Dispute** *(per transaction)* — a transaction peers disagree about including.\n\n###### Computation boundary:\n*Result: Per node — a count over the dashboard window, ranked worst-first.*\n*NOT recorded as a metric. Derived in the Grafana query by regex over raw log text.*\n\n###### Source:\n[Consensus.h](https://github.com/XRPLF/rippled/blob/develop/include/xrpl/consensus/Consensus.h)\n\n###### Function:\n`Consensus::timerEntry / checkConsensus`\n\n###### References:\n[Consensus](https://xrpl.org/docs/concepts/consensus-protocol)", + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 0, + "y": 21 + }, + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.service_instance_id}", + "unit": "suffix: events", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 100 + }, + { + "color": "red", + "value": 1000 + } + ] + }, + "custom": {} + }, + "overrides": [] + }, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "textMode": "value_and_name", + "wideLayout": true, + "reduceOptions": { + "calcs": ["max"], + "fields": "", + "values": false + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "refId": "A", + "expr": "topk($topn, sum by (service_instance_id) (count_over_time({service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | partition = `LedgerConsensus` | severity =~ `WRN|ERR|FTL` [$__range])))", + "instant": true, + "queryType": "instant" + } + ], + "id": 7 + }, + { + "type": "stat", + "title": "Nodes By Job Latency Breach", + "description": "###### What this is:\n*Which nodes are missing job latency targets most often.*\n\n###### How it's computed:\n*Count of LoadMonitor slow-job lines per node; the emitter only fires above a 500ms threshold.*\n\n###### Reading it:\n*A direct read on which node is most overloaded. Works at the default log level.*\n\n###### Healthy range:\n*Low. A busy node breaches occasionally.*\n\n###### Watch for:\n*A node far ahead of its peers, which usually means disk or CPU pressure local to it.*\n\n###### Keywords:\n- **Breach** *(per job)* — one job execution over the 500ms LoadMonitor threshold.\n\n###### Computation boundary:\n*Result: Per node — a count of BREACHES, never of total job executions.*\n*Derived in the Grafana query. Use the native `job_*` metrics for totals.*\n\n###### Source:\n[LoadMonitor.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/core/detail/LoadMonitor.cpp)\n\n###### Function:\n`LoadMonitor::addLoadSample`\n\n###### Note:\n*Counts breaches above 500ms only, so it is an exception count and not a latency measure.*\n\n###### References:\n[Job queue](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/core/detail/JobQueue.cpp)", + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 6, + "y": 21 + }, + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.service_instance_id}", + "unit": "suffix: breaches", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 50 + }, + { + "color": "red", + "value": 500 + } + ] + }, + "custom": {} + }, + "overrides": [] + }, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "textMode": "value_and_name", + "wideLayout": true, + "reduceOptions": { + "calcs": ["max"], + "fields": "", + "values": false + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "refId": "A", + "expr": "topk($topn, sum by (service_instance_id) (count_over_time({service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | partition = `LoadMonitor` |~ `Job: .* run: [0-9]+ms wait: [0-9]+ms` [$__range])))", + "instant": true, + "queryType": "instant" + } + ], + "id": 8 + }, + { + "type": "stat", + "title": "Nodes By Sync Instability", + "description": "###### What this is:\n*Which nodes are spending the most time out of the full state.*\n\n###### How it's computed:\n*Count of STATE-> transitions into any non-full state, per node.*\n\n###### Reading it:\n*A stable node holds full and emits nothing here, so any non-zero value means it left full that many times.*\n\n###### Healthy range:\n*Zero on a synced node.*\n\n###### Watch for:\n*A repeating count, which means the node is flapping rather than having had one bad moment.*\n\n###### Keywords:\n- **Operating mode** *(per node)* — Disconnected, Connected, Syncing, Tracking, Full.\n- **Flapping** *(per node)* — repeated departures from full.\n\n###### Computation boundary:\n*Result: Per node — a count over the dashboard window, ranked worst-first.*\n*NOT recorded as a metric. Derived in the Grafana query by regex over raw log text.*\n\n###### Source:\n[NetworkOPs.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/NetworkOPs.cpp)\n\n###### Function:\n`NetworkOPsImp::setMode`\n\n###### References:\n[Server states](https://xrpl.org/docs/concepts/networks-and-servers/rippled-server-states)", + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 12, + "y": 21 + }, + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.service_instance_id}", + "unit": "suffix: transitions", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 5 + }, + { + "color": "red", + "value": 30 + } + ] + }, + "custom": {} + }, + "overrides": [] + }, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "textMode": "value_and_name", + "wideLayout": true, + "reduceOptions": { + "calcs": ["max"], + "fields": "", + "values": false + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "refId": "A", + "expr": "topk($topn, sum by (service_instance_id) (count_over_time({service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" |~ `STATE->(syncing|connected|tracking|disconnected)` [$__range])))", + "instant": true, + "queryType": "instant" + } + ], + "id": 9 + }, + { + "type": "stat", + "title": "Nodes By Ledger Fetch Waste", + "description": "###### What this is:\n*Which nodes waste the most bandwidth fetching ledger data they already hold.*\n\n###### How it's computed:\n*Duplicate ledger nodes divided by total fetched, per node, from the InboundLedger stats lines.*\n\n###### Reading it:\n*A ratio of 0.8 means four in five fetched nodes were already present locally.*\n\n###### Healthy range:\n*Below roughly 0.3. Some duplication is unavoidable when fetching from several peers.*\n\n###### Watch for:\n*Sustained values above 0.8, which waste both bandwidth and peer resource credit.*\n\n###### Keywords:\n- **Duplicate** *(per fetch)* — a ledger node already held locally.\n\n###### Computation boundary:\n*Result: Per node — a RATIO of two summed counters, not a count.*\n*Derived in the Grafana query via `unwrap`.*\n\n###### Source:\n[InboundLedger.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/detail/InboundLedger.cpp)\n\n###### Function:\n`InboundLedger::onTimer`\n\n###### Note:\n*Requires debug logs on the InboundLedger partition.*\n\n###### References:\n[Ledgers](https://xrpl.org/docs/concepts/ledgers)", + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 18, + "y": 21 + }, + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.service_instance_id}", + "unit": "percentunit", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 0.5 + }, + { + "color": "red", + "value": 0.8 + } + ] + }, + "custom": {}, + "min": 0, + "max": 1 + }, + "overrides": [] + }, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "textMode": "value_and_name", + "wideLayout": true, + "reduceOptions": { + "calcs": ["mean"], + "fields": "", + "values": false + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "refId": "A", + "expr": "topk($topn, sum by (service_instance_id) (sum_over_time({service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | partition = `InboundLedger` | regexp `node stats: good:(?P[0-9]+) dupe:(?P[0-9]+)` | unwrap dupe [$__range])) / (sum by (service_instance_id) (sum_over_time({service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | partition = `InboundLedger` | regexp `node stats: good:(?P[0-9]+) dupe:(?P[0-9]+)` | unwrap dupe [$__range])) + sum by (service_instance_id) (sum_over_time({service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | partition = `InboundLedger` | regexp `node stats: good:(?P[0-9]+) dupe:(?P[0-9]+)` | unwrap good [$__range]))))", + "instant": true, + "queryType": "instant" + } + ], + "id": 10 + }, + { + "type": "row", + "title": "Node Operating State Transitions [DEFAULT OK]", + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 41 + }, + "panels": [], + "id": 11 + }, + { + "type": "timeseries", + "title": "Node State Transition Rate", + "description": "###### What this is:\n*Rate of node operating-state transitions: disconnected, connected, syncing, tracking, and full.*\n\n###### How it's computed:\n*Per-second count of `STATE->` log lines, with the target state parsed from the line.*\n\n###### Reading it:\n*Each line is one transition INTO that state. A stable node sits in full and emits nothing.*\n\n###### Healthy range:\n*Flat at zero once synced. Any non-zero value means the node is changing state.*\n\n###### Watch for:\n*A repeating full to syncing to tracking to full cycle, which means the node cannot hold sync. Live observation shows 110 transitions per state per day on a flapping node.*\n\n###### Keywords:\n- **Operating mode / server state** *(per node)* — the node's sync level: Disconnected, Connected, Syncing, Tracking, Full.\n- **Transition** *(per node)* — one `STATE->` log line, emitted only when the state actually changes.\n\n###### Computation boundary:\n*Result: Per node — each series counts one server's own transitions.*\n*NOT recorded as a metric anywhere. Derived entirely in the Grafana query by regex over raw log text; xrpld only writes the line.*\n\n###### Source:\n[NetworkOPs.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/NetworkOPs.cpp)\n\n###### Function:\n`NetworkOPsImp::setMode`\n\n###### References:\n[Server states](https://xrpl.org/docs/concepts/networks-and-servers/rippled-server-states)", + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 42 + }, + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.state} [${__field.labels.service_instance_id}]", + "unit": "suffix: transitions/s", + "min": 0, + "custom": { + "drawStyle": "line", + "lineWidth": 2, + "fillOpacity": 10, + "axisLabel": "Transitions / Sec", + "spanNulls": 1800000, + "insertNulls": false, + "showPoints": "auto", + "pointSize": 3 + }, + "color": { + "mode": "palette-classic" + } + }, + "overrides": [] + }, + "options": { + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + }, + "legend": { + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "calcs": ["lastNotNull", "max"] + } + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "refId": "A", + "expr": "sum by (state, service_instance_id) (rate({service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | severity =~ \"$severity\" | regexp `STATE->(?P\\w+)` | state != `` | label_format state=`{{if eq .state \"full\"}}Full{{else if eq .state \"syncing\"}}Syncing{{else if eq .state \"tracking\"}}Tracking{{else if eq .state \"connected\"}}Connected{{else if eq .state \"disconnected\"}}Disconnected{{else}}{{.state}}{{end}}` [$__auto]))" + } + ], + "id": 12 + }, + { + "type": "state-timeline", + "title": "Node State Timeline", + "description": "###### What this is:\n*The node's operating state over time, as a discrete timeline.*\n\n###### How it's computed:\n*The most recent `STATE->` transition in each interval, rendered as a state band.*\n\n###### Reading it:\n*Shows at a glance how long the node spent in each state and exactly when it left full.*\n\n###### Healthy range:\n*One unbroken full band across the window.*\n\n###### Watch for:\n*Any band that is not full, and repeated narrow bands, which indicate state flapping.*\n\n###### Keywords:\n- **Operating mode / server state** *(per node)* — the node's sync level: Disconnected, Connected, Syncing, Tracking, Full.\n\n###### Computation boundary:\n*Result: Per node — one band per server per state.*\n*NOT recorded as a metric anywhere. Derived in the Grafana query by regex over raw log text.*\n\n###### Source:\n[NetworkOPs.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/NetworkOPs.cpp)\n\n###### Function:\n`NetworkOPsImp::setMode`\n\n###### Note:\n*Only transitions are logged, so a node that never changes state produces no data here. Read with the transition-rate panel beside it.*\n\n###### References:\n[Server states](https://xrpl.org/docs/concepts/networks-and-servers/rippled-server-states)", + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 42 + }, + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.state} [${__field.labels.service_instance_id}]", + "custom": { + "fillOpacity": 80, + "lineWidth": 0, + "spanNulls": true + }, + "color": { + "mode": "palette-classic" + } + }, + "overrides": [] + }, + "options": { + "showValue": "auto", + "rowHeight": 0.9, + "mergeValues": true, + "alignValue": "center", + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + }, + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + } + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "refId": "A", + "expr": "sum by (state, service_instance_id) (count_over_time({service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | severity =~ \"$severity\" | regexp `STATE->(?P\\w+)` | state != `` | label_format state=`{{if eq .state \"full\"}}Full{{else if eq .state \"syncing\"}}Syncing{{else if eq .state \"tracking\"}}Tracking{{else if eq .state \"connected\"}}Connected{{else if eq .state \"disconnected\"}}Disconnected{{else}}{{.state}}{{end}}` [$__auto]))" + } + ], + "id": 13 + }, + { + "type": "row", + "title": "Log Volume & Severity Mix [DEFAULT OK]", + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 52 + }, + "panels": [], + "id": 14 + }, + { + "type": "timeseries", + "title": "Log Line Rate By Severity", + "description": "###### What this is:\n*Rate of log lines emitted by xrpld, split by severity.*\n\n###### How it's computed:\n*Per-second count of matching log lines grouped by the severity field parsed out of each line.*\n\n###### Reading it:\n*Use this to confirm the log pipeline is alive, and to see at a glance whether DBG lines are being collected at all.*\n\n###### Healthy range:\n*Workload-dependent. If the DBG series is absent, every panel in a [DBG] row on this dashboard will be empty.*\n\n###### Watch for:\n*A sudden collapse to only WRN and ERR, which means debug logging was turned off and the [DBG] rows have gone blind rather than quiet.*\n\n###### Keywords:\n- **Severity** *(per line)* — xrpld log level: DBG, NFO, WRN, ERR, FTL.\n- **Structured metadata** *(per line)* — Loki fields parsed from the line, filtered with `|` rather than in the stream selector.\n\n###### Computation boundary:\n*Result: Per node per severity — a count of log lines, not of events in the node.*\n*Derived in the Grafana query; the collector's filelog receiver parses severity, xrpld itself exports no such metric.*\n\n###### Source:\n[Log.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/basics/Log.cpp)\n\n###### Function:\n`Logs::Sink::write`\n\n###### References:\n[Loki structured metadata](https://grafana.com/docs/loki/latest/get-started/labels/structured-metadata/)", + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 53 + }, + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.severity} [${__field.labels.service_instance_id}]", + "unit": "suffix: lines/s", + "custom": { + "drawStyle": "line", + "lineWidth": 2, + "fillOpacity": 10, + "axisLabel": "Log Lines / Sec", + "spanNulls": 1800000, + "insertNulls": false, + "showPoints": "auto", + "pointSize": 3 + }, + "color": { + "mode": "palette-classic" + } + }, + "overrides": [] + }, + "options": { + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + }, + "legend": { + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "calcs": ["lastNotNull", "max"] + } + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "refId": "A", + "expr": "sum by (severity, service_instance_id) (rate({service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | severity =~ \"$severity\" | label_format severity=`{{if eq .severity \"DBG\"}}Debug{{else if eq .severity \"NFO\"}}Info{{else if eq .severity \"WRN\"}}Warning{{else if eq .severity \"ERR\"}}Error{{else if eq .severity \"FTL\"}}Fatal{{else}}{{.severity}}{{end}}` [$__auto]))" + } + ], + "id": 15, + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + } + }, + { + "type": "timeseries", + "title": "Log Line Rate By Partition (Top $topn)", + "description": "###### What this is:\n*The busiest xrpld log partitions by line rate.*\n\n###### How it's computed:\n*Per-second count of log lines grouped by the partition field, limited to the top N series.*\n\n###### Reading it:\n*Shows which subsystem dominates log volume, which is the main cost driver for Loki ingest.*\n\n###### Healthy range:\n*Workload-dependent. Resource, JobQueue, and LedgerConsensus are normally the loudest.*\n\n###### Watch for:\n*A partition suddenly dominating, which usually means a subsystem entered a retry or error loop.*\n\n###### Keywords:\n- **Partition** *(per line)* — the xrpld subsystem that emitted the line, e.g. ManifestCache, Resource, LedgerConsensus.\n\n###### Computation boundary:\n*Result: Per node per partition — a count of log lines.*\n*Derived in the Grafana query; truncated to the top N by Loki's series limit.*\n\n###### Source:\n[Log.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/basics/Log.cpp)\n\n###### Function:\n`Logs::Sink::write`\n\n###### References:\n[Loki log queries](https://grafana.com/docs/loki/latest/query/log_queries/)", + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 53 + }, + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.partition} [${__field.labels.service_instance_id}]", + "unit": "suffix: lines/s", + "custom": { + "drawStyle": "line", + "lineWidth": 2, + "fillOpacity": 10, + "axisLabel": "Log Lines / Sec", + "spanNulls": 1800000, + "insertNulls": false, + "showPoints": "auto", + "pointSize": 3 + }, + "color": { + "mode": "palette-classic" + } + }, + "overrides": [] + }, + "options": { + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + }, + "legend": { + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "calcs": ["lastNotNull", "max"] + } + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "refId": "A", + "expr": "topk($topn, sum by (partition, service_instance_id) (rate({service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | severity =~ \"$severity\" [$__auto])))" + } + ], + "id": 16, + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + } + }, + { + "type": "row", + "title": "Manifests — Disposition & Producers [DBG]", + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 63 + }, + "panels": [], + "id": 17 + }, + { + "type": "timeseries", + "title": "Manifest Disposition Rate", + "description": "###### What this is:\n*Rate of manifest apply outcomes: accepted, stale, revoked, invalid, or rate-limited.*\n\n###### How it's computed:\n*Per-second count of ManifestCache log lines, with the action parsed out of the `Manifest: ;Pk: ...` text.*\n\n###### Reading it:\n*AcceptedNew and AcceptedUpdate are useful work; Stale and UntrustedCapacity are rejections and normally dominate.*\n\n###### Healthy range:\n*Accepted rates are low and bursty. A high sustained Stale rate is normal — peers gossip manifests this node already holds.*\n\n###### Watch for:\n*Any Invalid, which means a signature failed verification, and a sustained UntrustedCapacity climb, which indicates a manifest flood from unlisted keys.*\n\n###### Keywords:\n- **Manifest** *(per validator)* — a signed record binding a validator's master key to its current signing key.\n- **Disposition** *(per manifest)* — the apply outcome: AcceptedNew, AcceptedUpdate, Stale, Revoked, Invalid, UntrustedCapacity.\n- **Stale** *(per manifest)* — sequence number not greater than the one already held; the common benign rejection.\n\n###### Computation boundary:\n*Result: Per node per action — counts log EVENTS, not distinct manifests.*\n*NOT recorded as a metric. `applyManifest` has no instrumentation; this is regex over `logMftAct` output in the Grafana query.*\n\n###### Source:\n[Manifest.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/server/Manifest.cpp)\n\n###### Function:\n`ManifestCache::applyManifest / logMftAct`\n\n###### References:\n[Validator keys](https://xrpl.org/docs/concepts/consensus-protocol/validator-keys)", + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 64 + }, + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.action} [${__field.labels.service_instance_id}]", + "unit": "suffix: manifests/s", + "custom": { + "drawStyle": "line", + "lineWidth": 2, + "fillOpacity": 10, + "axisLabel": "Manifests / Sec", + "spanNulls": 1800000, + "insertNulls": false, + "showPoints": "auto", + "pointSize": 3 + }, + "color": { + "mode": "palette-classic" + } + }, + "overrides": [] + }, + "options": { + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + }, + "legend": { + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "calcs": ["lastNotNull", "max"] + } + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "refId": "A", + "expr": "sum by (action, service_instance_id) (rate({service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | partition = `ManifestCache` | severity =~ \"$severity\" | regexp `Manifest: (?P[A-Za-z]+);` | action =~ \"$manifest_action\" [$__auto]))" + } + ], + "id": 18, + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + } + }, + { + "type": "timeseries", + "title": "Manifest Accept Vs Reject Rate", + "description": "###### What this is:\n*Manifest outcomes collapsed into two series: accepted versus rejected.*\n\n###### How it's computed:\n*Per-second counts of ManifestCache lines, with AcceptedNew and AcceptedUpdate summed as accepted and all other actions summed as rejected.*\n\n###### Reading it:\n*Gives the useful-work fraction of inbound manifest processing without per-action detail.*\n\n###### Healthy range:\n*Rejected normally far exceeds accepted; peers routinely re-gossip known manifests.*\n\n###### Watch for:\n*A rejected rate in the hundreds per second, which indicates a manifest flood consuming the JtManifest job queue.*\n\n###### Keywords:\n- **Accepted** *(per manifest)* — AcceptedNew or AcceptedUpdate; the manifest changed cache state.\n- **Rejected** *(per manifest)* — Stale, Invalid, Revoked, or UntrustedCapacity.\n\n###### Computation boundary:\n*Result: Per node — two summed series over the same log lines.*\n*Derived in the Grafana query. Accepted may legitimately be absent: measured 1,360 AcceptedNew per 7d, so short windows often contain none. Both series carry `or vector(0)` so a zero-accept window renders a flat zero line rather than disappearing.*\n\n###### Source:\n[Manifest.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/server/Manifest.cpp)\n\n###### Function:\n`ManifestCache::applyManifest / logMftAct`\n\n###### References:\n[Validator keys](https://xrpl.org/docs/concepts/consensus-protocol/validator-keys)", + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 64 + }, + "fieldConfig": { + "defaults": { + "unit": "suffix: manifests/s", + "custom": { + "drawStyle": "line", + "lineWidth": 2, + "fillOpacity": 10, + "axisLabel": "Manifests / Sec", + "spanNulls": 1800000, + "insertNulls": false, + "showPoints": "auto", + "pointSize": 3 + }, + "color": { + "mode": "palette-classic" + } + }, + "overrides": [] + }, + "options": { + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + }, + "legend": { + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "calcs": ["lastNotNull", "max"] + } + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "refId": "A", + "legendFormat": "Accepted [{{service_instance_id}}]", + "expr": "sum by (service_instance_id) (rate({service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | partition = `ManifestCache` | severity =~ \"$severity\" |~ `Manifest: Accepted` [$__auto])) or vector(0)" + }, + { + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "refId": "B", + "legendFormat": "Rejected [{{service_instance_id}}]", + "expr": "sum by (service_instance_id) (rate({service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | partition = `ManifestCache` | severity =~ \"$severity\" |~ `Manifest: (Stale|Invalid|Revoked|UntrustedCapacity)` [$__auto])) or vector(0)" + } + ], + "id": 19, + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + } + }, + { + "type": "table", + "title": "Top $topn Manifest Producers By Master Key", + "description": "###### What this is:\n*The master keys responsible for the most manifest log events in the selected window.*\n\n###### How it's computed:\n*Count of ManifestCache lines grouped by the base58 master key parsed from the log text, limited to the top N.\nCounts are per log EVENT, not per distinct manifest.*\n\n###### Reading it:\n*Identifies which validator keys generate the most manifest churn on this node.*\n\n###### Healthy range:\n*A small number of keys with modest repeat counts. Live observation shows roughly 19 to 24 events per key per hour.*\n\n###### Watch for:\n*A single unlisted key with a very high count, which is the signature of a targeted manifest flood.*\n\n###### Keywords:\n- **Master key** *(per validator)* — the long-lived base58 key a manifest is signed under.\n- **Log event** *(per line)* — one manifest apply attempt; the same key recurs as peers re-gossip.\n\n###### Computation boundary:\n*Result: Per master key per node — counts log EVENTS, NOT distinct manifests and NOT distinct keys.*\n*Derived in the Grafana query. Truncated to top N: Loki rejects queries returning over 2000 series, so a true distinct-key count is impossible here.*\n\n###### Source:\n[Manifest.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/server/Manifest.cpp)\n\n###### Function:\n`logMftAct`\n\n###### Note:\n*Truncated to the top N. Loki rejects a query returning over 2000 series, so a full distinct-key count is not possible here.*\n\n###### References:\n[Validator keys](https://xrpl.org/docs/concepts/consensus-protocol/validator-keys)", + "gridPos": { + "h": 14, + "w": 24, + "x": 0, + "y": 74 + }, + "fieldConfig": { + "defaults": { + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false, + "filterable": true + }, + "color": { + "mode": "thresholds" + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "text", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Last *" + }, + "properties": [ + { + "id": "displayName", + "value": "Log Events" + }, + { + "id": "custom.cellOptions", + "value": { + "type": "gauge", + "mode": "gradient" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "pk" + }, + "properties": [ + { + "id": "displayName", + "value": "Master Key" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "service_instance_id" + }, + "properties": [ + { + "id": "displayName", + "value": "Node" + } + ] + } + ] + }, + "options": { + "cellHeight": "sm", + "showHeader": true, + "sortBy": [ + { + "displayName": "Log Events", + "desc": true + } + ], + "footer": { + "show": false, + "reducer": ["sum"], + "countRows": false, + "fields": "" + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "transformations": [ + { + "id": "reduce", + "options": { + "reducers": ["lastNotNull"], + "labelsToFields": true, + "includeTimeField": false + } + }, + { + "id": "organize", + "options": { + "excludeByName": { + "Field": true, + "Time": true + } + } + } + ], + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "refId": "A", + "instant": true, + "queryType": "instant", + "expr": "topk($topn, sum by (pk, service_instance_id) (count_over_time({service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | partition = `ManifestCache` | severity =~ \"$severity\" | regexp `Manifest: (?P[A-Za-z]+);Pk: (?P[A-Za-z0-9]+);` | pk != `` | action =~ \"$manifest_action\" [$__range])))" + } + ], + "id": 20, + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + } + }, + { + "type": "row", + "title": "Resource Fee Charges — Load Attribution [DBG]", + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 88 + }, + "panels": [], + "id": 21 + }, + { + "type": "timeseries", + "title": "Fee Charge Rate By Reason", + "description": "###### What this is:\n*Rate at which this node charges peers a resource fee, split by the reason for the charge.*\n\n###### How it's computed:\n*Per-second count of Resource log lines, with the reason parsed from the `for ($)` text.*\n\n###### Reading it:\n*Shows what kind of peer behaviour is costing this node the most. Moderate peer request normally dominates.*\n\n###### Healthy range:\n*Workload-dependent and roughly proportional to peer count and request volume.*\n\n###### Watch for:\n*A rising useless data rate, which means peers are sending malformed or unwanted payloads.*\n\n###### Keywords:\n- **Resource fee** *(per peer)* — an internal load credit charged against a peer, unrelated to XRP transaction fees.\n- **Reason** *(per charge)* — why the charge was applied, e.g. moderate peer request, useless data.\n\n###### Computation boundary:\n*Result: Per node per reason — a count of charge events.*\n*NOT recorded as a metric. Derived in the Grafana query from `Logic::charge` log output.*\n\n###### Source:\n[ResourceManager.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/resource/detail/ResourceManager.cpp)\n\n###### Function:\n`Logic::charge`\n\n###### References:\n[Peer protocol](https://xrpl.org/docs/concepts/networks-and-servers/peer-protocol)", + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 89 + }, + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.reason} [${__field.labels.service_instance_id}]", + "unit": "suffix: charges/s", + "custom": { + "drawStyle": "line", + "lineWidth": 2, + "fillOpacity": 10, + "axisLabel": "Charges / Sec", + "spanNulls": 1800000, + "insertNulls": false, + "showPoints": "auto", + "pointSize": 3 + }, + "color": { + "mode": "palette-classic" + } + }, + "overrides": [] + }, + "options": { + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + }, + "legend": { + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "calcs": ["lastNotNull", "max"] + } + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "refId": "A", + "expr": "sum by (reason, service_instance_id) (rate({service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | partition = `Resource` | severity =~ \"$severity\" | regexp `for (?P[a-z ]+) \\(\\$(?P[0-9]+)\\)` | reason =~ \"$charge_reason\" | reason != `` | label_format reason=`{{if eq .reason \"useless data\"}}Useless Data{{else if eq .reason \"moderate peer request\"}}Moderate Peer Request{{else if eq .reason \"heavy peer request\"}}Heavy Peer Request{{else if eq .reason \"light peer request\"}}Light Peer Request{{else if eq .reason \"trivial peer request\"}}Trivial Peer Request{{else if eq .reason \"unwanted data\"}}Unwanted Data{{else if eq .reason \"init drop\"}}Init Drop{{else}}{{.reason}}{{end}}` [$__auto]))" + } + ], + "id": 22, + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + } + }, + { + "type": "timeseries", + "title": "Fee-Weighted Charge Load", + "description": "###### What this is:\n*Resource fee charges weighted by the fee amount, rather than counted equally.*\n\n###### How it's computed:\n*Per-second sum of the fee value parsed from each Resource charge line, using LogQL unwrap.*\n\n###### Reading it:\n*A single heavy peer request at 2000 outweighs eight moderate ones at 250, so this ranks real cost rather than event count.*\n\n###### Healthy range:\n*Workload-dependent; should track peer request volume smoothly.*\n\n###### Watch for:\n*Spikes that do not appear in the charge-count panel, which mean a shift toward expensive request types.*\n\n###### Keywords:\n- **Fee weight** *(per charge)* — the credit amount, e.g. 250 moderate, 2000 heavy; higher means costlier.\n\n###### Computation boundary:\n*Result: Per node per reason — a SUM of fee amounts, not a count of events.*\n*Derived in the Grafana query via LogQL `unwrap` over the parsed fee value.*\n\n###### Source:\n[ResourceManager.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/resource/detail/ResourceManager.cpp)\n\n###### Function:\n`Logic::charge`\n\n###### References:\n[Peer protocol](https://xrpl.org/docs/concepts/networks-and-servers/peer-protocol)", + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 89 + }, + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.reason} [${__field.labels.service_instance_id}]", + "unit": "suffix: fee/s", + "custom": { + "drawStyle": "line", + "lineWidth": 2, + "fillOpacity": 10, + "axisLabel": "Fee Units / Sec", + "spanNulls": 1800000, + "insertNulls": false, + "showPoints": "auto", + "pointSize": 3 + }, + "color": { + "mode": "palette-classic" + } + }, + "overrides": [] + }, + "options": { + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + }, + "legend": { + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "calcs": ["lastNotNull", "max"] + } + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "refId": "A", + "expr": "sum by (reason, service_instance_id) (sum_over_time({service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | partition = `Resource` | severity =~ \"$severity\" | regexp `for (?P[a-z ]+) \\(\\$(?P[0-9]+)\\)` | reason =~ \"$charge_reason\" | reason != `` | label_format reason=`{{if eq .reason \"useless data\"}}Useless Data{{else if eq .reason \"moderate peer request\"}}Moderate Peer Request{{else if eq .reason \"heavy peer request\"}}Heavy Peer Request{{else if eq .reason \"light peer request\"}}Light Peer Request{{else if eq .reason \"trivial peer request\"}}Trivial Peer Request{{else if eq .reason \"unwanted data\"}}Unwanted Data{{else if eq .reason \"init drop\"}}Init Drop{{else}}{{.reason}}{{end}}` | unwrap fee [$__auto]))" + } + ], + "id": 23, + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + } + }, + { + "type": "table", + "title": "Top $topn Charged Peers By IP And Public Key", + "description": "###### What this is:\n*The peers accumulating the most resource fee charges, identified by IP address and node public key.*\n\n###### How it's computed:\n*Sum of fee amounts parsed from Resource charge lines, grouped by IP and public key, limited to the top N.*\n\n###### Reading it:\n*This is the direct answer to which peer is costing this node the most, and is the primary abuse-attribution view.*\n\n###### Healthy range:\n*Charges spread across many peers with no single dominant entry.*\n\n###### Watch for:\n*One IP far above the rest, especially paired with the useless data reason, which indicates an abusive or misbehaving peer.*\n\n###### Keywords:\n- **Node public key** *(per peer)* — the peer's base58 identity, stable across reconnects.\n- **IP address** *(per peer)* — source address; the optional `:port` suffix is stripped so one peer is one row.\n\n###### Computation boundary:\n*Result: Per peer per node — a SUM of fee amounts over the dashboard window.*\n*Derived in the Grafana query. Truncated to top N by Loki's 2000-series limit, so this is the head of the distribution, never a total.*\n\n###### Source:\n[ResourceManager.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/resource/detail/ResourceManager.cpp)\n\n###### Function:\n`Logic::charge`\n\n###### Note:\n*Truncated to the top N because of Loki's 2000-series query limit.*\n\n###### References:\n[Peer protocol](https://xrpl.org/docs/concepts/networks-and-servers/peer-protocol)", + "gridPos": { + "h": 14, + "w": 24, + "x": 0, + "y": 99 + }, + "fieldConfig": { + "defaults": { + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false, + "filterable": true + }, + "color": { + "mode": "thresholds" + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "text", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Last *" + }, + "properties": [ + { + "id": "displayName", + "value": "Total Fee Charged" + }, + { + "id": "custom.cellOptions", + "value": { + "type": "gauge", + "mode": "gradient" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "ip" + }, + "properties": [ + { + "id": "displayName", + "value": "IP Address" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "pubkey" + }, + "properties": [ + { + "id": "displayName", + "value": "Public Key" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "service_instance_id" + }, + "properties": [ + { + "id": "displayName", + "value": "Node" + } + ] + } + ] + }, + "options": { + "cellHeight": "sm", + "showHeader": true, + "sortBy": [ + { + "displayName": "Total Fee Charged", + "desc": true + } + ], + "footer": { + "show": false, + "reducer": ["sum"], + "countRows": false, + "fields": "" + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "transformations": [ + { + "id": "reduce", + "options": { + "reducers": ["lastNotNull"], + "labelsToFields": true, + "includeTimeField": false + } + }, + { + "id": "organize", + "options": { + "excludeByName": { + "Field": true, + "Time": true + } + } + } + ], + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "refId": "A", + "instant": true, + "queryType": "instant", + "expr": "topk($topn, sum by (ip, pubkey, service_instance_id) (sum_over_time({service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | partition = `Resource` | severity =~ \"$severity\" | regexp `Charging IP Address: (?P[0-9a-fA-F:.]+?)(?::[0-9]+)?, Public Key: (?P[A-Za-z0-9]+) for (?P[a-z ]+) \\(\\$(?P[0-9]+)\\)` | reason =~ \"$charge_reason\" | unwrap fee [$__range])))" + } + ], + "id": 24, + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + } + }, + { + "type": "row", + "title": "Ledger Acquisition Efficiency [DBG]", + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 113 + }, + "panels": [], + "id": 25 + }, + { + "type": "timeseries", + "title": "Ledger Node Fetch Duplicate Ratio", + "description": "###### What this is:\n*The fraction of fetched ledger nodes that were duplicates this node already held.*\n\n###### How it's computed:\n*Sum of the dupe counter divided by the sum of good plus dupe, parsed from the `Ledger AS/TX node stats` log lines.*\n\n###### Reading it:\n*This is wasted ledger-fetch bandwidth. A ratio of 0.5 means half of everything fetched was already present.*\n\n###### Healthy range:\n*Below roughly 0.3. Some duplication is unavoidable when fetching from several peers at once.*\n\n###### Watch for:\n*Sustained values above 0.8. Live observation has recorded good:142 dupe:891, an 86 percent duplicate rate, which wastes both bandwidth and peer resource credit.*\n\n###### Keywords:\n- **Ledger node** *(per fetch)* — one SHAMap node fetched while acquiring a ledger.\n- **Duplicate** *(per fetch)* — a node already held locally; wasted bandwidth and peer credit.\n\n###### Computation boundary:\n*Result: Per node — a ratio of two summed counters, dupe / (good + dupe).*\n*NOT recorded as a metric. Derived in the Grafana query via `unwrap` over the counters in the InboundLedger stats line.*\n\n###### Source:\n[InboundLedger.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/detail/InboundLedger.cpp)\n\n###### Function:\n`InboundLedger::onTimer / takeAsRootNode`\n\n###### References:\n[Ledgers](https://xrpl.org/docs/concepts/ledgers)", + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 114 + }, + "fieldConfig": { + "defaults": { + "unit": "percentunit", + "min": 0, + "max": 1, + "custom": { + "drawStyle": "line", + "lineWidth": 2, + "fillOpacity": 10, + "axisLabel": "Duplicate Ratio", + "spanNulls": 1800000, + "insertNulls": false, + "showPoints": "auto", + "pointSize": 3 + }, + "color": { + "mode": "palette-classic" + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 0.5 + }, + { + "color": "red", + "value": 0.8 + } + ] + } + }, + "overrides": [] + }, + "options": { + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + }, + "legend": { + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "calcs": ["lastNotNull", "max"] + } + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "refId": "A", + "legendFormat": "Duplicate Ratio [{{service_instance_id}}]", + "expr": "sum by (service_instance_id) (sum_over_time({service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | partition = `InboundLedger` | severity =~ \"$severity\" | regexp `node stats: good:(?P[0-9]+) dupe:(?P[0-9]+)` | unwrap dupe [$__auto])) / (sum by (service_instance_id) (sum_over_time({service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | partition = `InboundLedger` | severity =~ \"$severity\" | regexp `node stats: good:(?P[0-9]+) dupe:(?P[0-9]+)` | unwrap dupe [$__auto])) + sum by (service_instance_id) (sum_over_time({service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | partition = `InboundLedger` | severity =~ \"$severity\" | regexp `node stats: good:(?P[0-9]+) dupe:(?P[0-9]+)` | unwrap good [$__auto])))" + } + ], + "id": 26, + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + } + }, + { + "type": "timeseries", + "title": "Ledger Node Fetch Rate — Good Vs Duplicate Vs Timeout", + "description": "###### What this is:\n*Absolute rate of useful ledger nodes fetched, duplicates received, and acquire timeouts.*\n\n###### How it's computed:\n*Per-second sums of the good, dupe, and timeouts counters parsed from InboundLedger acquire log lines.*\n\n###### Reading it:\n*Gives the absolute volumes behind the duplicate-ratio panel, so a high ratio at trivial volume can be told apart from a high ratio at scale.*\n\n###### Healthy range:\n*Good should exceed duplicate during active sync. Timeouts should stay near zero.*\n\n###### Watch for:\n*A rising timeout series, which means peers are not answering ledger requests and sync will stall.*\n\n###### Keywords:\n- **Good** *(per fetch)* — a useful, previously unheld ledger node.\n- **Timeout** *(per acquire)* — a ledger request a peer never answered.\n\n###### Computation boundary:\n*Result: Per node — SUMS of the parsed counters, giving absolute volumes behind the ratio panel.*\n*Derived in the Grafana query via `unwrap`.*\n\n###### Source:\n[InboundLedger.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/detail/InboundLedger.cpp)\n\n###### Function:\n`InboundLedger::onTimer`\n\n###### References:\n[Ledgers](https://xrpl.org/docs/concepts/ledgers)", + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 114 + }, + "fieldConfig": { + "defaults": { + "unit": "suffix: nodes/s", + "custom": { + "drawStyle": "line", + "lineWidth": 2, + "fillOpacity": 10, + "axisLabel": "Ledger Nodes / Sec", + "spanNulls": 1800000, + "insertNulls": false, + "showPoints": "auto", + "pointSize": 3 + }, + "color": { + "mode": "palette-classic" + } + }, + "overrides": [] + }, + "options": { + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + }, + "legend": { + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "calcs": ["lastNotNull", "max"] + } + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "refId": "A", + "legendFormat": "Good [{{service_instance_id}}]", + "expr": "sum by (service_instance_id) (sum_over_time({service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | partition = `InboundLedger` | severity =~ \"$severity\" | regexp `node stats: good:(?P[0-9]+)` | unwrap good [$__auto]))" + }, + { + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "refId": "B", + "legendFormat": "Duplicate [{{service_instance_id}}]", + "expr": "sum by (service_instance_id) (sum_over_time({service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | partition = `InboundLedger` | severity =~ \"$severity\" | regexp `node stats: good:(?P[0-9]+) dupe:(?P[0-9]+)` | unwrap dupe [$__auto]))" + }, + { + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "refId": "C", + "legendFormat": "Acquire Timeouts [{{service_instance_id}}]", + "expr": "sum by (service_instance_id) (sum_over_time({service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | partition = `InboundLedger` | severity =~ \"$severity\" | regexp `Acquire \\S+ timeouts:(?P[0-9]+)` | unwrap timeouts [$__auto]))" + } + ], + "id": 27, + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + } + }, + { + "type": "row", + "title": "Peer Lifecycle & Disconnects [DBG]", + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 124 + }, + "panels": [], + "id": 28 + }, + { + "type": "timeseries", + "title": "Peer Disconnect Rate By Reason", + "description": "###### What this is:\n*Rate of peer connection endings, split by the reason recorded in the log.*\n\n###### How it's computed:\n*Per-second count of Peer log lines matching Timeout, Closed, or a refused connection attempt.*\n\n###### Reading it:\n*Distinguishes clean teardown from failure. Closed is a normal ending; Timeout and Connection refused are not.*\n\n###### Healthy range:\n*Closed dominant with a low, steady background of the others.*\n\n###### Watch for:\n*A Timeout rate approaching the Closed rate, which points at network trouble or unresponsive peers.*\n\n###### Keywords:\n- **Closed** *(per peer)* — a clean connection teardown; the normal ending.\n- **Timeout** *(per peer)* — the peer stopped responding.\n- **Connection refused** *(per attempt)* — an outbound attempt the remote rejected.\n\n###### Computation boundary:\n*Result: Per node per outcome — a count of peer lifecycle events.*\n*Derived in the Grafana query. Note `overlay_peer_disconnects` exists as a metric but carries no reason breakdown, which is what this panel adds.*\n\n###### Source:\n[PeerImp.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/PeerImp.cpp)\n\n###### Function:\n`PeerImp::close / onTimer`\n\n###### References:\n[Peer protocol](https://xrpl.org/docs/concepts/networks-and-servers/peer-protocol)", + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 125 + }, + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.outcome} [${__field.labels.service_instance_id}]", + "unit": "suffix: events/s", + "custom": { + "drawStyle": "line", + "lineWidth": 2, + "fillOpacity": 10, + "axisLabel": "Peer Events / Sec", + "spanNulls": 1800000, + "insertNulls": false, + "showPoints": "auto", + "pointSize": 3 + }, + "color": { + "mode": "palette-classic" + } + }, + "overrides": [] + }, + "options": { + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + }, + "legend": { + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "calcs": ["lastNotNull", "max"] + } + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "refId": "A", + "expr": "sum by (outcome, service_instance_id) (rate({service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | partition = `Peer` | severity =~ \"$severity\" | regexp `\\] (?PTimeout|Closed|onConnect: Connection refused)` | outcome != `` | label_format outcome=`{{if eq .outcome \"Closed\"}}Closed{{else if eq .outcome \"Timeout\"}}Timeout{{else if eq .outcome \"onConnect: Connection refused\"}}Connection Refused{{else}}{{.outcome}}{{end}}` [$__auto]))" + } + ], + "id": 29, + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + } + }, + { + "type": "timeseries", + "title": "Peer Handshake And Accept Rate", + "description": "###### What this is:\n*Rate of completed peer handshakes and accepted inbound connections.*\n\n###### How it's computed:\n*Per-second count of PeerFinder log lines matching handshake and accept events.*\n\n###### Reading it:\n*Read together with the disconnect panel: healthy churn has handshakes roughly balancing disconnects.*\n\n###### Healthy range:\n*Low and steady once the node has a full peer slate.*\n\n###### Watch for:\n*A high handshake rate alongside a high disconnect rate, which means peers connect and immediately drop.*\n\n###### Keywords:\n- **Handshake** *(per peer)* — protocol negotiation completed with a peer.\n- **Accept** *(per peer)* — an inbound connection admitted to a peer slot.\n\n###### Computation boundary:\n*Result: Per node — counts of PeerFinder events.*\n*Derived in the Grafana query.*\n\n###### Source:\n[Logic.h](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/peerfinder/detail/Logic.h)\n\n###### Function:\n`Logic::on_handshake / on_accept`\n\n###### References:\n[Peer protocol](https://xrpl.org/docs/concepts/networks-and-servers/peer-protocol)", + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 125 + }, + "fieldConfig": { + "defaults": { + "unit": "suffix: events/s", + "custom": { + "drawStyle": "line", + "lineWidth": 2, + "fillOpacity": 10, + "axisLabel": "Peer Events / Sec", + "spanNulls": 1800000, + "insertNulls": false, + "showPoints": "auto", + "pointSize": 3 + }, + "color": { + "mode": "palette-classic" + } + }, + "overrides": [] + }, + "options": { + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + }, + "legend": { + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "calcs": ["lastNotNull", "max"] + } + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "refId": "A", + "legendFormat": "Handshake [{{service_instance_id}}]", + "expr": "sum by (service_instance_id) (rate({service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | partition = `PeerFinder` | severity =~ \"$severity\" |~ `Logic handshake` [$__auto]))" + }, + { + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "refId": "B", + "legendFormat": "Accept [{{service_instance_id}}]", + "expr": "sum by (service_instance_id) (rate({service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | partition = `PeerFinder` | severity =~ \"$severity\" |~ `Logic accept` [$__auto]))" + } + ], + "id": 30, + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + } + }, + { + "type": "row", + "title": "Consensus Phase & Mode [DEFAULT OK]", + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 135 + }, + "panels": [], + "id": 31 + }, + { + "type": "timeseries", + "title": "Consensus Phase Transition Rate", + "description": "###### What this is:\n*Rate of consensus phase transitions into Open, Establish, and Accepted.*\n\n###### How it's computed:\n*Per-second count of log lines announcing a ConsensusPhase transition, grouped by the target phase.*\n\n###### Reading it:\n*A healthy node cycles Open to Establish to Accepted once per ledger, so all three series should track together.*\n\n###### Healthy range:\n*Roughly one transition per phase per ledger interval, near 0.25 per second on a 4-second close.*\n\n###### Watch for:\n*Establish transitions outnumbering Accepted, which means rounds start but fail to converge.*\n\n###### Keywords:\n- **Consensus phase** *(per round)* — Open collects transactions, Establish converges on a set, Accepted applies it.\n- **Transition** *(per round)* — one phase change; a healthy node cycles all three once per ledger.\n\n###### Computation boundary:\n*Result: Per node per phase — a count of transitions.*\n*Derived in the Grafana query by regex over the phase-change log line.*\n\n###### Source:\n[Consensus.h](https://github.com/XRPLF/rippled/blob/develop/include/xrpl/consensus/Consensus.h)\n\n###### Function:\n`Consensus::phase transition logging`\n\n###### References:\n[Consensus](https://xrpl.org/docs/concepts/consensus-protocol)", + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 136 + }, + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.phase} [${__field.labels.service_instance_id}]", + "unit": "suffix: transitions/s", + "custom": { + "drawStyle": "line", + "lineWidth": 2, + "fillOpacity": 10, + "axisLabel": "Transitions / Sec", + "spanNulls": 1800000, + "insertNulls": false, + "showPoints": "auto", + "pointSize": 3 + }, + "color": { + "mode": "palette-classic" + } + }, + "overrides": [] + }, + "options": { + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + }, + "legend": { + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "calcs": ["lastNotNull", "max"] + } + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "refId": "A", + "expr": "sum by (phase, service_instance_id) (rate({service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | severity =~ \"$severity\" | regexp `[Tt]ransitioned to ConsensusPhase::(?P\\w+)` | phase =~ \"$consensus_phase\" [$__auto]))" + } + ], + "id": 32, + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + } + }, + { + "type": "timeseries", + "title": "Consensus Operating Mode Rate", + "description": "###### What this is:\n*Rate of consensus heartbeat log lines, split by the operating mode reported in each.*\n\n###### How it's computed:\n*Per-second count of ConsensusLogger heartbeat lines, with the mode parsed from the `mode: ` text.*\n\n###### Reading it:\n*This acts as a mode-occupancy proxy: the heartbeat fires about once per second, so the dominant series is the node's current mode.*\n\n###### Healthy range:\n*Almost entirely the full series on a synced node.*\n\n###### Watch for:\n*Any sustained syncing, observing, or tracking share, which means the node is not participating normally.*\n\n###### Keywords:\n- **Consensus mode** *(per node)* — the node's participation level: full proposing, observing, syncing, tracking.\n- **Heartbeat** *(per second)* — the ConsensusLogger timer line, emitted roughly once per second.\n\n###### Computation boundary:\n*Result: Per node per mode — a count of heartbeat lines, used as a mode-occupancy PROXY rather than a true duration.*\n*Derived in the Grafana query. For exact durations use the state_accounting metrics on the Node Health dashboard.*\n\n###### Source:\n[NetworkOPs.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/NetworkOPs.cpp)\n\n###### Function:\n`ConsensusLogger heartbeat`\n\n###### References:\n[Consensus](https://xrpl.org/docs/concepts/consensus-protocol)", + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 136 + }, + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.mode} [${__field.labels.service_instance_id}]", + "unit": "suffix: beats/s", + "custom": { + "drawStyle": "line", + "lineWidth": 2, + "fillOpacity": 10, + "axisLabel": "Heartbeats / Sec", + "spanNulls": 1800000, + "insertNulls": false, + "showPoints": "auto", + "pointSize": 3 + }, + "color": { + "mode": "palette-classic" + } + }, + "overrides": [] + }, + "options": { + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + }, + "legend": { + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "calcs": ["lastNotNull", "max"] + } + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "refId": "A", + "expr": "sum by (mode, service_instance_id) (rate({service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | partition = `NetworkOPs` | severity =~ \"$severity\" | regexp `ConsensusLogger.*?mode: (?P\\w+)\\.` | mode =~ \"$consensus_mode\" | mode != `` | label_format mode=`{{if eq .mode \"full\"}}Full{{else if eq .mode \"syncing\"}}Syncing{{else if eq .mode \"observing\"}}Observing{{else if eq .mode \"tracking\"}}Tracking{{else}}{{.mode}}{{end}}` [$__auto]))" + } + ], + "id": 33, + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + } + }, + { + "type": "timeseries", + "title": "Validator List Quorum And Trusted Set Size", + "description": "###### What this is:\n*The quorum threshold and trusted validator count this node computed, as recorded in the log.*\n\n###### How it's computed:\n*Quorum and total are parsed from the `Using quorum of N for new set of M trusted validators` line and plotted as values.*\n\n###### Reading it:\n*Confirms the node agrees with the network on how many validators it trusts and how many must agree.*\n\n###### Healthy range:\n*Stable, with quorum at roughly 80 percent of the trusted total.*\n\n###### Watch for:\n*A drop in the trusted total, which shrinks the quorum and weakens the node's safety margin.*\n\n###### Keywords:\n- **Quorum** *(per node)* — how many trusted validators must agree; normally about 80 percent of the trusted set.\n- **Trusted set** *(per node)* — validators from the UNL this node currently trusts.\n\n###### Computation boundary:\n*Result: Per node — the last VALUE parsed from the log line, not a rate or count.*\n*Derived in the Grafana query via `unwrap`. Overlaps the `unl_quorum` metric; prefer that metric for the value and this panel for churn events.*\n\n###### Source:\n[ValidatorList.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/detail/ValidatorList.cpp)\n\n###### Function:\n`ValidatorList::updateTrusted`\n\n###### References:\n[Unique Node List](https://xrpl.org/docs/concepts/consensus-protocol/unique-node-list)", + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 146 + }, + "fieldConfig": { + "defaults": { + "unit": "suffix: validators", + "min": 0, + "custom": { + "drawStyle": "line", + "lineWidth": 2, + "fillOpacity": 0, + "axisLabel": "Validators", + "spanNulls": 1800000, + "insertNulls": false, + "showPoints": "auto", + "pointSize": 3 + }, + "color": { + "mode": "palette-classic" + } + }, + "overrides": [] + }, + "options": { + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + }, + "legend": { + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "calcs": ["lastNotNull", "min", "max"] + } + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "refId": "A", + "legendFormat": "Quorum [{{service_instance_id}}]", + "expr": "max by (service_instance_id) (max_over_time({service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | partition = `ValidatorList` | severity =~ \"$severity\" | regexp `Using quorum of (?P[0-9]+) for new set of (?P[0-9]+) trusted` | unwrap quorum [$__auto]))" + }, + { + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "refId": "B", + "legendFormat": "Trusted Validators [{{service_instance_id}}]", + "expr": "max by (service_instance_id) (max_over_time({service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | partition = `ValidatorList` | severity =~ \"$severity\" | regexp `Using quorum of (?P[0-9]+) for new set of (?P[0-9]+) trusted` | unwrap total [$__auto]))" + } + ], + "id": 34, + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + } + }, + { + "type": "row", + "title": "Slow Job Latency Breaches [DEFAULT OK]", + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 156 + }, + "panels": [], + "id": 35 + }, + { + "type": "timeseries", + "title": "Slow Job Run Time (p99)", + "description": "###### What this is:\n*The 99th percentile run time of jobs that breached their latency target.*\n\n###### How it's computed:\n*Quantile over the run millisecond value parsed from LoadMonitor job lines, grouped by job name.*\n\n###### Reading it:\n*Identifies which job types are the slowest when they do run long.*\n\n###### Healthy range:\n*Only a handful of job names should appear at all. Values in the tens of seconds indicate real stalls.*\n\n###### Watch for:\n*ProcessLData or InboundLedger dominating, which is the signature of the cold-read sync bottleneck.*\n\n###### Keywords:\n- **Job** *(per task)* — a unit of work on xrpld's job queue, e.g. ProcessLData, InboundLedger.\n- **Run time** *(per job)* — time spent executing, excluding queue wait.\n- **Breach** *(per job)* — an execution exceeding the 500ms LoadMonitor threshold; only these are logged.\n\n###### Computation boundary:\n*Result: Per node per job type — a p99 over BREACHES ONLY, never over all executions.*\n*Derived in the Grafana query via `unwrap`. For total job counts and latencies use the native `job_*` metrics on the Job Queue Analysis dashboard.*\n\n###### Source:\n[LoadMonitor.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/core/detail/LoadMonitor.cpp)\n\n###### Function:\n`LoadMonitor::addLoadSample`\n\n###### Note:\n*These lines are only emitted above a 500ms latency threshold, so this panel counts BREACHES, never total job executions. Use the Job Queue Analysis dashboard for totals.*\n\n###### References:\n[Job queue](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/core/detail/JobQueue.cpp)", + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 157 + }, + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.jobname} [${__field.labels.service_instance_id}]", + "unit": "ms", + "min": 0, + "custom": { + "drawStyle": "line", + "lineWidth": 2, + "fillOpacity": 10, + "axisLabel": "Run Time (ms)", + "spanNulls": 1800000, + "insertNulls": false, + "showPoints": "auto", + "pointSize": 3 + }, + "color": { + "mode": "palette-classic" + } + }, + "overrides": [] + }, + "options": { + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + }, + "legend": { + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "calcs": ["lastNotNull", "max"] + } + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "refId": "A", + "expr": "quantile_over_time(0.99, {service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | partition = `LoadMonitor` | severity =~ \"$severity\" | regexp `Job: (?P\\S+) run: (?P[0-9]+)ms wait: (?P[0-9]+)ms` | unwrap run [$__auto]) by (jobname, service_instance_id)" + } + ], + "id": 36, + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + } + }, + { + "type": "timeseries", + "title": "Slow Job Breach Rate By Job Name (Top $topn)", + "description": "###### What this is:\n*How often each job type breaches its latency target.*\n\n###### How it's computed:\n*Per-second count of LoadMonitor job lines grouped by job name, limited to the top N.*\n\n###### Reading it:\n*Read with the p99 panel: a job can breach rarely but severely, or often but mildly.*\n\n###### Healthy range:\n*Near zero. Any sustained non-zero rate means a job type is routinely missing its target.*\n\n###### Watch for:\n*A step change after a deploy, which points at a regression in that job's path.*\n\n###### Keywords:\n- **Breach** *(per job)* — an execution over the 500ms threshold.\n- **Job type** *(per task)* — the named job class, e.g. sweep, ProcessLData.\n\n###### Computation boundary:\n*Result: Per node per job type — a count of BREACHES, not of executions.*\n*Derived in the Grafana query; truncated to top N.*\n\n###### Source:\n[LoadMonitor.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/core/detail/LoadMonitor.cpp)\n\n###### Function:\n`LoadMonitor::addLoadSample`\n\n###### Note:\n*Counts BREACHES above the 500ms threshold, not total job executions.*\n\n###### References:\n[Job queue](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/core/detail/JobQueue.cpp)", + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 157 + }, + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.jobname} [${__field.labels.service_instance_id}]", + "unit": "suffix: breaches/s", + "custom": { + "drawStyle": "line", + "lineWidth": 2, + "fillOpacity": 10, + "axisLabel": "Breaches / Sec", + "spanNulls": 1800000, + "insertNulls": false, + "showPoints": "auto", + "pointSize": 3 + }, + "color": { + "mode": "palette-classic" + } + }, + "overrides": [] + }, + "options": { + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + }, + "legend": { + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "calcs": ["lastNotNull", "max"] + } + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "refId": "A", + "expr": "topk($topn, sum by (jobname, service_instance_id) (rate({service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | partition = `LoadMonitor` | severity =~ \"$severity\" | regexp `Job: (?P\\S+) run: (?P[0-9]+)ms wait: (?P[0-9]+)ms` | jobname != `` [$__auto])))" + } + ], + "id": 37, + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + } + }, + { + "type": "timeseries", + "title": "Slow Job Queue Wait Time (p99)", + "description": "###### What this is:\n*The 99th percentile queue wait time of jobs that breached their latency target.*\n\n###### How it's computed:\n*Quantile over the wait millisecond value parsed from LoadMonitor job lines, grouped by job name.*\n\n###### Reading it:\n*Separates queueing delay from execution cost. High wait with low run means the job queue is saturated rather than the work being slow.*\n\n###### Healthy range:\n*Near zero. Live observation shows wait at 0ms for most breaches, meaning run time is the cause.*\n\n###### Watch for:\n*Wait time rising above run time, which shifts the diagnosis from slow work to a saturated job queue.*\n\n###### Keywords:\n- **Wait time** *(per job)* — time queued before execution began.\n- **Run versus wait** *(per job)* — high wait means a saturated queue; high run means slow work.\n\n###### Computation boundary:\n*Result: Per node per job type — a p99 over BREACHES ONLY.*\n*Derived in the Grafana query via `unwrap`.*\n\n###### Source:\n[LoadMonitor.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/core/detail/LoadMonitor.cpp)\n\n###### Function:\n`LoadMonitor::addLoadSample`\n\n###### Note:\n*Counts BREACHES above the 500ms threshold, not total job executions.*\n\n###### References:\n[Job queue](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/core/detail/JobQueue.cpp)", + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 167 + }, + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.jobname} [${__field.labels.service_instance_id}]", + "unit": "ms", + "min": 0, + "custom": { + "drawStyle": "line", + "lineWidth": 2, + "fillOpacity": 10, + "axisLabel": "Queue Wait Time (ms)", + "spanNulls": 1800000, + "insertNulls": false, + "showPoints": "auto", + "pointSize": 3 + }, + "color": { + "mode": "palette-classic" + } + }, + "overrides": [] + }, + "options": { + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + }, + "legend": { + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "calcs": ["lastNotNull", "max"] + } + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "refId": "A", + "expr": "quantile_over_time(0.99, {service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | partition = `LoadMonitor` | severity =~ \"$severity\" | regexp `Job: (?P\\S+) run: (?P[0-9]+)ms wait: (?P[0-9]+)ms` | unwrap wait [$__auto]) by (jobname, service_instance_id)" + } + ], + "id": 38, + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + } + }, + { + "type": "row", + "title": "Error & Warning Stream [DEFAULT OK]", + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 177 + }, + "panels": [], + "id": 39 + }, + { + "type": "timeseries", + "title": "Warning And Error Rate By Partition (Top $topn)", + "description": "###### What this is:\n*Rate of WRN, ERR, and FTL log lines by partition.*\n\n###### How it's computed:\n*Per-second count of log lines at warning severity or above, grouped by partition and limited to the top N.*\n\n###### Reading it:\n*This is the one row that works fully at default log level, so it is the first place to look on an unmodified node.*\n\n###### Healthy range:\n*Low and flat. LoadMonitor warnings are expected on a busy node.*\n\n###### Watch for:\n*Any ERR or FTL series appearing, and step changes in a partition that is normally quiet.*\n\n###### Keywords:\n- **Severity** *(per line)* — WRN, ERR, or FTL; the levels a default-configured node still writes.\n- **Partition** *(per line)* — the emitting xrpld subsystem.\n\n###### Computation boundary:\n*Result: Per node per partition per severity — a count of log lines.*\n*Derived in the Grafana query. This row is the only one that works fully at default log level.*\n\n###### Source:\n[Log.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/basics/Log.cpp)\n\n###### Function:\n`Logs::Sink::write`\n\n###### References:\n[Loki log queries](https://grafana.com/docs/loki/latest/query/log_queries/)", + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 178 + }, + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.partition} ${__field.labels.severity} [${__field.labels.service_instance_id}]", + "unit": "suffix: lines/s", + "custom": { + "drawStyle": "line", + "lineWidth": 2, + "fillOpacity": 10, + "axisLabel": "Log Lines / Sec", + "spanNulls": 1800000, + "insertNulls": false, + "showPoints": "auto", + "pointSize": 3 + }, + "color": { + "mode": "palette-classic" + }, + "mappings": [ + { + "type": "value", + "options": { + "WRN": { + "text": "Warning", + "index": 0 + }, + "ERR": { + "text": "Error", + "index": 1 + }, + "FTL": { + "text": "Fatal", + "index": 2 + } + } + } + ] + }, + "overrides": [] + }, + "options": { + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + }, + "legend": { + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "calcs": ["lastNotNull", "max"] + } + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "refId": "A", + "expr": "topk($topn, sum by (partition, severity, service_instance_id) (rate({service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | severity =~ `WRN|ERR|FTL` [$__auto])))" + } + ], + "id": 40, + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + } + }, + { + "type": "logs", + "title": "Recent Warnings And Errors", + "description": "###### What this is:\n*The most recent log lines at warning severity or above.*\n\n###### How it's computed:\n*Raw log stream filtered to WRN, ERR, and FTL, newest first.*\n\n###### Reading it:\n*Use this to read the actual text behind a spike in the rate panel beside it.*\n\n###### Healthy range:\n*Sparse. LoadMonitor slow-job warnings are the common benign entry.*\n\n###### Watch for:\n*Repeating identical errors, which indicate a stuck retry loop rather than a transient fault.*\n\n###### Keywords:\n- **Log stream** *(per node)* — the raw lines behind the aggregate rates, newest first.\n\n###### Computation boundary:\n*Result: Raw log lines, no aggregation.*\n*Rendered directly from Loki; no computation applied.*\n\n###### Source:\n[Log.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/basics/Log.cpp)\n\n###### Function:\n`Logs::Sink::write`\n\n###### References:\n[Loki log queries](https://grafana.com/docs/loki/latest/query/log_queries/)", + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 178 + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "options": { + "showTime": true, + "showLabels": false, + "showCommonLabels": false, + "wrapLogMessage": true, + "prettifyLogMessage": false, + "enableLogDetails": true, + "dedupStrategy": "none", + "sortOrder": "Descending" + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + }, + "refId": "A", + "expr": "{service_name=~\"$service_name\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\"} | xrpl_network_type =~ \"$xrpl_network_type\" | severity =~ `WRN|ERR|FTL` | line_format `{{.partition}}:{{.severity}} {{.message}}`" + } + ], + "id": 41, + "datasource": { + "type": "loki", + "uid": "${DS_LOKI}" + } + } + ] +} diff --git a/docker/telemetry/grafana/dashboards/network-traffic.json b/docker/telemetry/grafana/dashboards/network-traffic.json index 724f05f652..a74a889a6c 100644 --- a/docker/telemetry/grafana/dashboards/network-traffic.json +++ b/docker/telemetry/grafana/dashboards/network-traffic.json @@ -1,8 +1,38 @@ { "annotations": { - "list": [] + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + }, + { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": false, + "iconColor": "rgb(70, 70, 70)", + "name": "Annotate perf-iac runs", + "target": { + "limit": 100, + "matchAny": false, + "tags": ["perf-iac"], + "type": "tags" + }, + "type": "tags" + } + ] }, - "description": "What this shows: Peer connectivity and overlay bandwidth for this node: peer counts, disconnects, total bytes and messages exchanged, and the transaction, proposal, and validation traffic that flows across the peer-to-peer network.\nUse it to: Gauge peer health and overall overlay bandwidth and see which message types dominate network traffic.", + "description": "What this shows: Peer connectivity and overlay bandwidth for this node: peer counts, disconnects, total bytes and messages exchanged, and the transaction, proposal, and validation traffic that flows across the peer-to-peer network. \u2014 Use it to: Gauge peer health and overall overlay bandwidth and see which message types dominate network traffic.", "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 1, @@ -11,7 +41,7 @@ "panels": [ { "title": "Active Peers", - "description": "###### What this is:\n*Number of active inbound and outbound peer connections the node currently holds.*\n\n###### How it's computed:\n*Current value of the inbound and outbound active-peer counts per node.*\n\n###### Reading it:\n*Outbound is what the node dials out; inbound is what others open to it. Both should be stable.*\n\n###### Healthy range:\n*roughly 10-21 outbound and 0-85 inbound on mainnet, depending on config.*\n\n###### Watch for:\n*Outbound dropping toward zero (isolation) or inbound pinned at the limit with churn (connection pressure).*\n\n###### Source:\n[PeerfinderManager.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/peerfinder/detail/PeerfinderManager.cpp)\n\n###### Function:\n`Logic Stats ctor`", + "description": "###### What this is:\n*Number of active inbound and outbound peer connections the node currently holds.*\n\n###### How it's computed:\n*Current value of the inbound and outbound active-peer counts per node.*\n\n###### Reading it:\n*Outbound is what the node dials out; inbound is what others open to it. Both should be stable.*\n\n###### Healthy range:\n*roughly 10-21 outbound and 0-85 inbound on mainnet, depending on config.*\n\n###### Watch for:\n*Outbound dropping toward zero (isolation) or inbound pinned at the limit with churn (connection pressure).*\n\n###### Keywords:\n- **Peer** *(per node)* \u2014 another server this node holds a protocol connection to.\n- **Overlay** *(network-wide)* \u2014 the peer-to-peer mesh xrpld nodes form to gossip transactions, proposals and validations.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[PeerfinderManager.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/peerfinder/detail/PeerfinderManager.cpp)\n\n###### Function:\n`Logic Stats ctor`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#peer)", "type": "timeseries", "gridPos": { "h": 8, @@ -29,20 +59,22 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(peer_finder_active_inbound_peers{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Inbound Peers\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(peer_finder_active_inbound_peers{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Inbound Peers\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(peer_finder_active_outbound_peers{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Outbound Peers\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(peer_finder_active_outbound_peers{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Outbound Peers\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "short", "custom": { "axisLabel": "Peers", @@ -57,308 +89,8 @@ }, { "title": "Peer Disconnects", - "description": "###### What this is:\n*Peer connections that dropped, as a per-interval increase.*\n\n###### How it's computed:\n*Disconnect events per rate-interval, per node.*\n\n###### Reading it:\n*A flat or slowly rising line is normal; the slope matters more than the absolute value.*\n\n###### Healthy range:\n*workload-dependent; slow, steady growth.*\n\n###### Watch for:\n*Sharp step-ups in the slope (network instability, resource exhaustion, or many peers dropping the node at once).*\n\n###### Source:\n[OverlayImpl.h](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.h)\n\n###### Function:\n`OverlayImpl::Stats ctor`", + "description": "###### What this is:\n*Peer connections that dropped, as a per-interval increase.*\n\n###### How it's computed:\n*Disconnect events per rate-interval, per node.*\n\n###### Reading it:\n*A flat or slowly rising line is normal; the slope matters more than the absolute value.*\n\n###### Healthy range:\n*workload-dependent; slow, steady growth.*\n\n###### Watch for:\n*Sharp step-ups in the slope (network instability, resource exhaustion, or many peers dropping the node at once).*\n\n###### Keywords:\n- **Peer** *(per node)* \u2014 another server this node holds a protocol connection to.\n- **Overlay** *(network-wide)* \u2014 the peer-to-peer mesh xrpld nodes form to gossip transactions, proposals and validations.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[OverlayImpl.h](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.h)\n\n###### Function:\n`OverlayImpl::Stats ctor`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#peer)", "type": "timeseries", - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 24 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(increase(overlay_peer_disconnects{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Disconnects\", \"\", \"\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "short", - "custom": { - "axisLabel": "Disconnects per interval", - "spanNulls": 1800000, - "insertNulls": false, - "showPoints": "auto", - "pointSize": 3 - } - }, - "overrides": [] - } - }, - { - "title": "Total Network Bytes", - "description": "###### What this is:\n*Total bytes received and sent across all peer connections.*\n\n###### How it's computed:\n*Per-second in/out byte rate per node.*\n\n###### Reading it:\n*Overall bandwidth footprint; in and out usually track network activity together.*\n\n###### Healthy range:\n*workload-dependent.*\n\n###### Watch for:\n*Sudden sustained jumps not matched by ledger or transaction activity (relay storms or a noisy peer).*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`", - "type": "timeseries", - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 8 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(rate(total_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Bytes In\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(rate(total_bytes_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Bytes Out\", \"\", \"\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "Bps", - "custom": { - "axisLabel": "Bytes", - "spanNulls": 1800000, - "insertNulls": false, - "showPoints": "auto", - "pointSize": 3 - } - }, - "overrides": [] - } - }, - { - "title": "Total Network Messages", - "description": "###### What this is:\n*Total messages received and sent across all peer connections.*\n\n###### How it's computed:\n*Per-second in/out message rate per node.*\n\n###### Reading it:\n*Overall message throughput of the overlay; complements the byte totals.*\n\n###### Healthy range:\n*workload-dependent.*\n\n###### Watch for:\n*Message count climbing far faster than bytes (many tiny messages, possible flooding).*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`", - "type": "timeseries", - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 8 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(rate(total_messages_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Messages In\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(rate(total_messages_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Messages Out\", \"\", \"\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "suffix: messages/s", - "custom": { - "axisLabel": "Messages / Sec", - "spanNulls": 1800000, - "insertNulls": false, - "showPoints": "auto", - "pointSize": 3 - } - }, - "overrides": [] - } - }, - { - "title": "Transaction Traffic", - "description": "###### What this is:\n*Transaction relay messages in and out, plus duplicate transaction messages received.*\n\n###### How it's computed:\n*Per-second message rate for the transaction and transaction-duplicate categories.*\n\n###### Reading it:\n*In/out rise with network transaction volume; duplicates are transactions the node already had.*\n\n###### Healthy range:\n*workload-dependent; duplicates a modest fraction of inbound.*\n\n###### Watch for:\n*Duplicate inbound approaching or exceeding unique inbound (redundant relay), or a sharp spike suggesting transaction flooding.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`", - "type": "timeseries", - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 16 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(rate(transactions_messages_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"TX Messages In\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(rate(transactions_messages_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"TX Messages Out\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(rate(transactions_duplicate_messages_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"TX Duplicate In\", \"\", \"\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "suffix: messages/s", - "custom": { - "axisLabel": "Messages / Sec", - "spanNulls": 1800000, - "insertNulls": false, - "showPoints": "auto", - "pointSize": 3 - } - }, - "overrides": [] - } - }, - { - "title": "Proposal Traffic", - "description": "###### What this is:\n*Consensus proposal messages in/out, plus untrusted and duplicate proposal messages received.*\n\n###### How it's computed:\n*Per-second message rate for the proposal, proposal-untrusted, and proposal-duplicate categories.*\n\n###### Reading it:\n*Trusted in/out track consensus rounds; untrusted come from validators not on this node's trusted list.*\n\n###### Healthy range:\n*workload-dependent; untrusted and duplicates low relative to trusted.*\n\n###### Watch for:\n*High untrusted (trusted-list misconfiguration) or high duplicates (inefficient relay or proposal spam).*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`", - "type": "timeseries", - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 16 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(rate(proposals_messages_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Proposals In\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(rate(proposals_messages_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Proposals Out\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(rate(proposals_untrusted_messages_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Untrusted In\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(rate(proposals_duplicate_messages_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Duplicate In\", \"\", \"\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "suffix: messages/s", - "custom": { - "axisLabel": "Messages / Sec", - "spanNulls": 1800000, - "insertNulls": false, - "showPoints": "auto", - "pointSize": 3 - } - }, - "overrides": [] - } - }, - { - "title": "Validation Traffic", - "description": "###### What this is:\n*Validation messages in/out, plus untrusted and duplicate validation messages received.*\n\n###### How it's computed:\n*Per-second message rate for the validation, validation-untrusted, and validation-duplicate categories.*\n\n###### Reading it:\n*Trusted validations should arrive steadily each ledger; untrusted come from non-trusted validators.*\n\n###### Healthy range:\n*workload-dependent; untrusted and duplicates low relative to trusted.*\n\n###### Watch for:\n*Rising untrusted or duplicate validations (trusted-list health issues or validation spam).*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`", - "type": "timeseries", - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 24 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(rate(validations_messages_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Validations In\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(rate(validations_messages_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Validations Out\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(rate(validations_untrusted_messages_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Untrusted In\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(rate(validations_duplicate_messages_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Duplicate In\", \"\", \"\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "suffix: messages/s", - "custom": { - "axisLabel": "Messages / Sec", - "spanNulls": 1800000, - "insertNulls": false, - "showPoints": "auto", - "pointSize": 3 - } - }, - "overrides": [] - } - }, - { - "title": "Overlay Traffic by Category (Bytes In)", - "description": "###### What this is:\n*Top overlay traffic categories ranked by inbound bytes, excluding the all-traffic total.*\n\n###### How it's computed:\n*Top categories by inbound byte rate per node.*\n\n###### Reading it:\n*Shows which message types dominate receive bandwidth right now.*\n\n###### Healthy range:\n*workload-dependent; transactions, proposals, and validations typically lead on a synced node.*\n\n###### Watch for:\n*A fetch or ledger-data category topping the list (sync activity) or an unexpected category dominating.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`", - "type": "bargauge", "gridPos": { "h": 8, "w": 12, @@ -375,9 +107,326 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(topk(10, rate({service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", __name__=~\".*_bytes_in\", __name__!~\"total_.*\"}[$__rate_interval])), \"series\", \"$1\", \"__name__\", \"(.*)\")" + "expr": "label_replace(label_join(label_replace(increase(overlay_peer_disconnects{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Disconnects\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" + } + ], + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "unit": "short", + "custom": { + "axisLabel": "Disconnects per interval", + "spanNulls": 1800000, + "insertNulls": false, + "showPoints": "auto", + "pointSize": 3 + } + }, + "overrides": [] + } + }, + { + "title": "Total Network Bytes", + "description": "###### What this is:\n*Total bytes received and sent across all peer connections.*\n\n###### How it's computed:\n*Per-second in/out byte rate per node.*\n\n###### Reading it:\n*Overall bandwidth footprint; in and out usually track network activity together.*\n\n###### Healthy range:\n*workload-dependent.*\n\n###### Watch for:\n*Sudden sustained jumps not matched by ledger or transaction activity (relay storms or a noisy peer).*\n\n###### Keywords:\n- **Overlay** *(network-wide)* \u2014 the peer-to-peer mesh xrpld nodes form to gossip transactions, proposals and validations.\n- **Peer** *(per node)* \u2014 another server this node holds a protocol connection to.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#overlay)", + "type": "timeseries", + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "options": { + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(rate(total_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Bytes In\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(rate(total_bytes_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Bytes Out\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" + } + ], + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "unit": "Bps", + "custom": { + "axisLabel": "Bytes", + "spanNulls": 1800000, + "insertNulls": false, + "showPoints": "auto", + "pointSize": 3 + } + }, + "overrides": [] + } + }, + { + "title": "Total Network Messages", + "description": "###### What this is:\n*Total messages received and sent across all peer connections.*\n\n###### How it's computed:\n*Per-second in/out message rate per node.*\n\n###### Reading it:\n*Overall message throughput of the overlay; complements the byte totals.*\n\n###### Healthy range:\n*workload-dependent.*\n\n###### Watch for:\n*Message count climbing far faster than bytes (many tiny messages, possible flooding).*\n\n###### Keywords:\n- **Overlay** *(per node)* \u2014 the peer-to-peer network layer over which nodes exchange transactions, proposals, and validations.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`\n\n###### References:\n[Overlay](https://xrpl.org/docs/concepts/networks-and-servers/peer-protocol) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#overlay)", + "type": "timeseries", + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "options": { + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(rate(total_messages_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Messages In\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(rate(total_messages_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Messages Out\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" + } + ], + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "unit": "suffix: messages/s", + "custom": { + "axisLabel": "Messages / Sec", + "spanNulls": 1800000, + "insertNulls": false, + "showPoints": "auto", + "pointSize": 3 + } + }, + "overrides": [] + } + }, + { + "title": "Transaction Traffic", + "description": "###### What this is:\n*Transaction relay messages in and out, plus duplicate transaction messages received.*\n\n###### How it's computed:\n*Per-second message rate for the transaction and transaction-duplicate categories.*\n\n###### Reading it:\n*In/out rise with network transaction volume; duplicates are transactions the node already had.*\n\n###### Healthy range:\n*workload-dependent; duplicates a modest fraction of inbound.*\n\n###### Watch for:\n*Duplicate inbound approaching or exceeding unique inbound (redundant relay), or a sharp spike suggesting transaction flooding.*\n\n###### Keywords:\n- **Trusted / untrusted / duplicate** *(per node)* \u2014 message classification: trusted (from UNL validators), untrusted (others), or duplicate (already seen).\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`\n\n###### References:\n[Trusted / untrusted / duplicate](https://xrpl.org/docs/concepts/consensus-protocol/unl) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#trusted-untrusted-duplicate)", + "type": "timeseries", + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 16 + }, + "options": { + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(rate(transactions_messages_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"TX Messages In\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(rate(transactions_messages_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"TX Messages Out\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(rate(transactions_duplicate_messages_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"TX Duplicate In\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" + } + ], + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "unit": "suffix: messages/s", + "custom": { + "axisLabel": "Messages / Sec", + "spanNulls": 1800000, + "insertNulls": false, + "showPoints": "auto", + "pointSize": 3 + } + }, + "overrides": [] + } + }, + { + "title": "Proposal Traffic", + "description": "###### What this is:\n*Consensus proposal messages in/out, plus untrusted and duplicate proposal messages received.*\n\n###### How it's computed:\n*Per-second message rate for the proposal, proposal-untrusted, and proposal-duplicate categories.*\n\n###### Reading it:\n*Trusted in/out track consensus rounds; untrusted come from validators not on this node's trusted list.*\n\n###### Healthy range:\n*workload-dependent; untrusted and duplicates low relative to trusted.*\n\n###### Watch for:\n*High untrusted (trusted-list misconfiguration) or high duplicates (inefficient relay or proposal spam).*\n\n###### Keywords:\n- **Consensus** *(network event)* \u2014 the protocol by which validators agree on the next ledger's transaction set and close time.\n- **Proposal** *(network event)* \u2014 a validator's advertised set of candidate transactions for the next ledger, revised each round.\n- **Trusted / untrusted / duplicate** *(per node)* \u2014 message classification: trusted (from UNL validators), untrusted (others), or duplicate (already seen).\n- **Consensus round** *(network event)* \u2014 one propose-and-revise iteration of consensus; several may run before validators converge on a ledger.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`\n\n###### References:\n[Consensus](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) \u00b7 [Trusted / untrusted / duplicate](https://xrpl.org/docs/concepts/consensus-protocol/unl) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#consensus)", + "type": "timeseries", + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 16 + }, + "options": { + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(rate(proposals_messages_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Proposals In\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(rate(proposals_messages_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Proposals Out\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(rate(proposals_untrusted_messages_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Untrusted In\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(rate(proposals_duplicate_messages_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Duplicate In\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" + } + ], + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "unit": "suffix: messages/s", + "custom": { + "axisLabel": "Messages / Sec", + "spanNulls": 1800000, + "insertNulls": false, + "showPoints": "auto", + "pointSize": 3 + } + }, + "overrides": [] + } + }, + { + "title": "Validation Traffic", + "description": "###### What this is:\n*Validation messages in/out, plus untrusted and duplicate validation messages received.*\n\n###### How it's computed:\n*Per-second message rate for the validation, validation-untrusted, and validation-duplicate categories.*\n\n###### Reading it:\n*Trusted validations should arrive steadily each ledger; untrusted come from non-trusted validators.*\n\n###### Healthy range:\n*workload-dependent; untrusted and duplicates low relative to trusted.*\n\n###### Watch for:\n*Rising untrusted or duplicate validations (trusted-list health issues or validation spam).*\n\n###### Keywords:\n- **Trusted / untrusted / duplicate** *(per node)* \u2014 message classification: trusted (from UNL validators), untrusted (others), or duplicate (already seen).\n- **UNL (Unique Node List)** *(per node)* \u2014 the list of validators a node trusts not to collude; the basis for its consensus and quorum.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`\n\n###### References:\n[Trusted / untrusted / duplicate](https://xrpl.org/docs/concepts/consensus-protocol/unl) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#trusted-untrusted-duplicate)", + "type": "timeseries", + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 24 + }, + "options": { + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(rate(validations_messages_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Validations In\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(rate(validations_messages_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Validations Out\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(rate(validations_untrusted_messages_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Untrusted In\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(rate(validations_duplicate_messages_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Duplicate In\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" + } + ], + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "unit": "suffix: messages/s", + "custom": { + "axisLabel": "Messages / Sec", + "spanNulls": 1800000, + "insertNulls": false, + "showPoints": "auto", + "pointSize": 3 + } + }, + "overrides": [] + } + }, + { + "title": "Overlay Traffic by Category (Bytes In)", + "description": "###### What this is:\n*Top overlay traffic categories ranked by inbound bytes, excluding the all-traffic total.*\n\n###### How it's computed:\n*Top categories by inbound byte rate per node.*\n\n###### Reading it:\n*Shows which message types dominate receive bandwidth right now.*\n\n###### Healthy range:\n*workload-dependent; transactions, proposals, and validations typically lead on a synced node.*\n\n###### Watch for:\n*A fetch or ledger-data category topping the list (sync activity) or an unexpected category dominating.*\n\n###### Keywords:\n- **Overlay** *(per node)* \u2014 the peer-to-peer network layer over which nodes exchange transactions, proposals, and validations.\n- **Proposal** *(network event)* \u2014 a validator's advertised set of candidate transactions for the next ledger, revised each round.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`\n\n###### References:\n[Overlay](https://xrpl.org/docs/concepts/networks-and-servers/peer-protocol) \u00b7 [Proposal](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#overlay)", + "type": "bargauge", + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 24 + }, + "options": { + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(topk(10, label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(transactions_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"transactions_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(proposals_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"proposals_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(validations_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"validations_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(overhead_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"overhead_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(overhead_overlay_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"overhead_overlay_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(ping_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"ping_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(status_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"status_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(getobject_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"getobject_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(havetxset_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"havetxset_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(ledgerdata_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"ledgerdata_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(ledger_share_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"ledger_share_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(ledger_data_get_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"ledger_data_get_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(ledger_data_share_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"ledger_data_share_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(ledger_data_account_state_node_get_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"ledger_data_account_state_node_get_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(ledger_data_account_state_node_share_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"ledger_data_account_state_node_share_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(ledger_data_transaction_node_get_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"ledger_data_transaction_node_get_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(ledger_data_transaction_node_share_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"ledger_data_transaction_node_share_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(ledger_data_transaction_set_candidate_get_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"ledger_data_transaction_set_candidate_get_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(ledger_account_state_node_share_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"ledger_account_state_node_share_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(ledger_transaction_set_candidate_share_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"ledger_transaction_set_candidate_share_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(ledger_transaction_node_share_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"ledger_transaction_node_share_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(set_get_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"set_get_bytes_in\",\"\",\"\")), \"series\", \"$1\", \"__name__\", \"(.*)\")" } ], "fieldConfig": { @@ -654,7 +703,7 @@ }, { "title": "Duplicate Traffic (Wasted Bandwidth)", - "description": "###### What this is:\n*Throughput of duplicate transaction, proposal, and validation traffic: messages the node had already seen and discarded.*\n\n###### How it's computed:\n*Per-second rate of the duplicate byte counters for each category, in and out.*\n\n###### Reading it:\n*Lower is better; this is bandwidth spent on redundant relays.*\n\n###### Healthy range:\n*workload-dependent; a small fraction of total traffic.*\n\n###### Watch for:\n*Duplicate rate climbing toward the same order as useful traffic (poor relay topology or redundant flooding).*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`", + "description": "###### What this is:\n*Throughput of duplicate transaction, proposal, and validation traffic: messages the node had already seen and discarded.*\n\n###### How it's computed:\n*Per-second rate of the duplicate byte counters for each category, in and out.*\n\n###### Reading it:\n*Lower is better; this is bandwidth spent on redundant relays.*\n\n###### Healthy range:\n*workload-dependent; a small fraction of total traffic.*\n\n###### Watch for:\n*Duplicate rate climbing toward the same order as useful traffic (poor relay topology or redundant flooding).*\n\n###### Keywords:\n- **Trusted / untrusted / duplicate** *(per node)* \u2014 message classification: trusted (from UNL validators), untrusted (others), or duplicate (already seen).\n- **Proposal** *(network event)* \u2014 a validator's advertised set of candidate transactions for the next ledger, revised each round.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`\n\n###### References:\n[Trusted / untrusted / duplicate](https://xrpl.org/docs/concepts/consensus-protocol/unl) \u00b7 [Proposal](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#trusted-untrusted-duplicate)", "type": "timeseries", "gridPos": { "h": 8, @@ -672,44 +721,50 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(transactions_duplicate_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"TX Duplicate In\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(transactions_duplicate_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"TX Duplicate In\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(transactions_duplicate_bytes_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"TX Duplicate Out\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(transactions_duplicate_bytes_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"TX Duplicate Out\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(proposals_duplicate_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Proposals Duplicate In\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(proposals_duplicate_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Proposals Duplicate In\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(proposals_duplicate_bytes_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Proposals Duplicate Out\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(proposals_duplicate_bytes_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Proposals Duplicate Out\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(validations_duplicate_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Validations Duplicate In\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(validations_duplicate_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Validations Duplicate In\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(validations_duplicate_bytes_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Validations Duplicate Out\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(validations_duplicate_bytes_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Validations Duplicate Out\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "Bps", "custom": { "axisLabel": "Throughput", @@ -724,7 +779,7 @@ }, { "title": "All Traffic Categories (Detail)", - "description": "###### What this is:\n*The busiest overlay categories by inbound byte rate over time, excluding the all-traffic total.*\n\n###### How it's computed:\n*Per-second inbound byte rate for the busiest categories, ranked, excluding the all-traffic total.*\n\n###### Reading it:\n*Time-series companion to the category bar view; shows how the traffic mix shifts over the window.*\n\n###### Healthy range:\n*workload-dependent.*\n\n###### Watch for:\n*A category ramping up and staying high, or the mix suddenly changing (sync, spam, or a misbehaving peer).*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`", + "description": "###### What this is:\n*The busiest overlay categories by inbound byte rate over time, excluding the all-traffic total.*\n\n###### How it's computed:\n*Per-second inbound byte rate for the busiest categories, ranked, excluding the all-traffic total.*\n\n###### Reading it:\n*Time-series companion to the category bar view; shows how the traffic mix shifts over the window.*\n\n###### Healthy range:\n*workload-dependent.*\n\n###### Watch for:\n*A category ramping up and staying high, or the mix suddenly changing (sync, spam, or a misbehaving peer).*\n\n###### Keywords:\n- **Overlay** *(per node)* \u2014 the peer-to-peer network layer over which nodes exchange transactions, proposals, and validations.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`\n\n###### References:\n[Overlay](https://xrpl.org/docs/concepts/networks-and-servers/peer-protocol) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#overlay)", "type": "timeseries", "gridPos": { "h": 8, @@ -742,14 +797,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(topk(15, rate({__name__=~\".*_bytes_in\", __name__!~\"total_.*\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])), \"series\", \"$1\", \"__name__\", \"(.*)\")" + "expr": "label_replace(label_join(label_replace(topk(15, label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(getobject_account_state_node_get_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"getobject_account_state_node_get_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(getobject_account_state_node_share_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"getobject_account_state_node_share_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(getobject_cas_get_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"getobject_cas_get_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(getobject_cas_share_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"getobject_cas_share_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(getobject_fetch_pack_get_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"getobject_fetch_pack_get_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(getobject_fetch_pack_share_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"getobject_fetch_pack_share_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(getobject_get_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"getobject_get_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(getobject_ledger_get_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"getobject_ledger_get_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(getobject_ledger_share_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"getobject_ledger_share_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(getobject_share_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"getobject_share_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(getobject_transaction_get_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"getobject_transaction_get_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(getobject_transaction_node_get_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"getobject_transaction_node_get_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(getobject_transaction_node_share_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"getobject_transaction_node_share_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(getobject_transaction_share_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"getobject_transaction_share_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(getobject_transactions_get_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"getobject_transactions_get_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(have_transactions_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"have_transactions_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(ledger_account_state_node_get_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"ledger_account_state_node_get_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(ledger_account_state_node_share_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"ledger_account_state_node_share_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(ledger_data_account_state_node_get_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"ledger_data_account_state_node_get_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(ledger_data_account_state_node_share_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"ledger_data_account_state_node_share_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(ledger_data_get_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"ledger_data_get_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(ledger_data_share_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"ledger_data_share_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(ledger_data_transaction_node_get_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"ledger_data_transaction_node_get_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(ledger_data_transaction_node_share_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"ledger_data_transaction_node_share_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(ledger_data_transaction_set_candidate_get_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"ledger_data_transaction_set_candidate_get_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(ledger_data_transaction_set_candidate_share_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"ledger_data_transaction_set_candidate_share_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(ledger_get_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"ledger_get_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(ledger_share_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"ledger_share_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(ledger_transaction_node_get_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"ledger_transaction_node_get_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(ledger_transaction_node_share_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"ledger_transaction_node_share_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(ledger_transaction_set_candidate_get_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"ledger_transaction_set_candidate_get_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(ledger_transaction_set_candidate_share_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"ledger_transaction_set_candidate_share_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(overhead_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"overhead_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(overhead_cluster_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"overhead_cluster_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(overhead_manifest_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"overhead_manifest_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(overhead_overlay_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"overhead_overlay_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(proof_path_request_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"proof_path_request_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(proof_path_response_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"proof_path_response_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(proposals_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"proposals_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(proposals_duplicate_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"proposals_duplicate_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(proposals_untrusted_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"proposals_untrusted_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(replay_delta_request_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"replay_delta_request_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(replay_delta_response_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"replay_delta_response_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(requested_transactions_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"requested_transactions_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(set_get_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"set_get_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(set_share_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"set_share_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(squelch_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"squelch_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(squelch_ignored_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"squelch_ignored_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(squelch_suppressed_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"squelch_suppressed_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(transactions_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"transactions_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(transactions_duplicate_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"transactions_duplicate_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(unknown_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"unknown_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(validations_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"validations_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(validations_duplicate_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"validations_duplicate_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(validations_untrusted_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"validations_untrusted_bytes_in\",\"\",\"\") or label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role)(rate(validator_lists_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])),\"__name__\",\"validator_lists_bytes_in\",\"\",\"\")), \"series\", \"$1\", \"__name__\", \"(.*)\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "Bps", "custom": { "axisLabel": "Throughput", @@ -767,6 +823,19 @@ "tags": ["network"], "templating": { "list": [ + { + "name": "DS_PROMETHEUS", + "type": "datasource", + "label": "Prometheus", + "query": "prometheus", + "regex": "", + "current": {}, + "hide": 0, + "refresh": 1, + "includeAll": false, + "multi": false, + "options": [] + }, { "name": "service_name", "label": "Service Name", @@ -775,7 +844,7 @@ "query": "label_values(service_name)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -795,7 +864,7 @@ "query": "label_values(deployment_environment)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -815,7 +884,7 @@ "query": "label_values(xrpl_network_type)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -895,7 +964,7 @@ "query": "label_values(target_info, service_instance_id)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -914,5 +983,6 @@ "to": "now" }, "title": "Network Traffic", - "uid": "network-traffic" + "uid": "network-traffic", + "refresh": "30s" } diff --git a/docker/telemetry/grafana/dashboards/node-health.json b/docker/telemetry/grafana/dashboards/node-health.json index 200b723ee0..41597900e1 100644 --- a/docker/telemetry/grafana/dashboards/node-health.json +++ b/docker/telemetry/grafana/dashboards/node-health.json @@ -1,43 +1,55 @@ { "annotations": { - "list": [] + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + }, + { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": false, + "iconColor": "rgb(70, 70, 70)", + "name": "Annotate perf-iac runs", + "target": { + "limit": 100, + "matchAny": false, + "tags": ["perf-iac"], + "type": "tags" + }, + "type": "tags" + } + ] }, - "description": "What this shows: Operational health of an XRPL node: sync state, ledger progress, caches, storage, job queue, and network economy.\nUse it to: Get an at-a-glance read on whether the node is healthy, synced, and keeping up with the network.", + "description": "What this shows: Operational health of an XRPL node: sync state, ledger progress, caches, storage, job queue, and network economy. \u2014 Use it to: Get an at-a-glance read on whether the node is healthy, synced, and keeping up with the network.", "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 1, - "id": null, "links": [], "panels": [ { - "title": "Node Health", + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, "description": "###### What this is:\n*A single at-a-glance verdict: is this node healthy and doing its job (fully synced and keeping up with the network)?*\n\n###### How it's computed:\n*1 (Healthy) only when server_state == Full AND the validated ledger age is under 30s; otherwise 0 (Not Healthy). Combines server_info{metric=\"server_state\"} and ledgermaster_validated_ledger_age.*\n\n###### Reading it:\n*Green \"Healthy\" = full sync and current. Red \"Not Healthy\" = not full, or lagging the network (catching up, flapping, or stalled).*\n\n###### Healthy range:\n*Healthy (green) in steady state.*\n\n###### Watch for:\n*Any sustained Not Healthy \u2014 drill into the Operating Mode and Validated Ledger Age panels below to see whether it is a state or a lag problem.*\n\n###### Keywords:\n- **Full** *(per node)* \u2014 the node has the current validated ledger and complete recent history.\n- **Validated ledger age** *(per node)* \u2014 seconds since the last freshly validated ledger.\n\n###### Computation boundary:\n*Result: Per node \u2014 a boolean AND of two native metrics.*\n*Recorded in xrpld code as native metrics (beast::insight); the collector only forwards them; the Grafana query combines them.*\n\n###### Source:\n[app/misc/NetworkOPs.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/NetworkOPs.cpp)\n\n###### Function:\n`NetworkOPsImp::setMode / getValidatedLedgerAge`\n\n###### References:\n[Server states](https://xrpl.org/docs/concepts/networks-and-servers/rippled-server-states) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md)", - "type": "stat", - "gridPos": { - "h": 4, - "w": 24, - "x": 0, - "y": 0 - }, - "options": { - "colorMode": "background", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": ["lastNotNull"], - "fields": "", - "values": false - }, - "textMode": "value_and_name", - "wideLayout": true - }, "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "color": { "mode": "thresholds" }, + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "mappings": [ { "type": "value", @@ -68,9 +80,32 @@ } ] } - }, - "overrides": [] + } }, + "gridPos": { + "h": 4, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 129, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value_and_name", + "wideLayout": true, + "tooltip": {} + }, + "pluginVersion": "13.2.0-28926505616", "targets": [ { "datasource": { @@ -79,42 +114,95 @@ }, "expr": "label_replace(label_join(label_replace((server_info{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"server_state\"} == bool 4) * on(service_instance_id) (ledgermaster_validated_ledger_age{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"} < bool 30), \"series\", \"Node Health\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } - ] + ], + "title": "Node Health", + "type": "stat" }, { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*How old the most recently validated ledger is, in seconds.*\n\n###### How it's computed:\n*Current value of the validated-ledger-age gauge, refreshed each collection interval.*\n\n###### Reading it:\n*Lower is better; it should stay near the network close interval.*\n\n###### Healthy range:\n*Under about 7 seconds on a healthy node.*\n\n###### Watch for:\n*Values above 20 seconds mean the node is falling behind the network.*\n\n###### Keywords:\n- **Validated ledger** *(network-wide)* \u2014 a ledger confirmed final by the trusted validator quorum; its contents never change.\n- **Ledger close interval** *(network-wide)* \u2014 the network's steady ledger rhythm \u2014 roughly one closed ledger every 3-5 seconds on Mainnet.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[LedgerMaster.h](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/LedgerMaster.h)\n\n###### Function:\n`LedgerMaster::Stats::collectMetrics`\n\n###### References:\n[Validated ledger](https://xrpl.org/docs/concepts/ledgers/open-closed-validated-ledgers) \u00b7 [Ledger close interval](https://xrpl.org/docs/concepts/ledgers/ledger-close-times) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#validated-ledger)", + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "yellow", + "value": 10 + }, + { + "color": "red", + "value": 20 + } + ] + }, + "unit": "s" + } + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 0, + "y": 4 + }, + "id": 67, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + }, + "wideLayout": true + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(ledgermaster_validated_ledger_age{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Validated Age\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" + } + ], "title": "Validated Ledger Age", - "description": "###### What this is:\n*How old the most recently validated ledger is, in seconds.*\n\n###### How it's computed:\n*Current value of the validated-ledger-age gauge, refreshed each collection interval.*\n\n###### Reading it:\n*Lower is better; it should stay near the network close interval.*\n\n###### Healthy range:\n*Under about 7 seconds on a healthy node.*\n\n###### Watch for:\n*Values above 20 seconds mean the node is falling behind the network.*\n\n###### Source:\n[LedgerMaster.h](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/LedgerMaster.h)\n\n###### Function:\n`LedgerMaster::Stats::collectMetrics`", - "type": "stat", - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 4 + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(ledgermaster_validated_ledger_age{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Validated Age\", \"\", \"\")" - } - ], + "description": "###### What this is:\n*How old the most recently published ledger is, in seconds.*\n\n###### How it's computed:\n*Current value of the published-ledger-age gauge, refreshed each collection interval.*\n\n###### Reading it:\n*Lower is better; should track close to the validated ledger age.*\n\n###### Healthy range:\n*Under about 7 seconds on a healthy node.*\n\n###### Watch for:\n*A growing gap above the validated age indicates the publish pipeline is backing up.*\n\n###### Keywords:\n- **Published ledger** *(per node)* \u2014 the most recent validated ledger the node has finished publishing to its subscribers.\n- **Validated ledger** *(network-wide)* \u2014 a ledger confirmed final by the trusted validator quorum; its contents never change.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[LedgerMaster.h](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/LedgerMaster.h)\n\n###### Function:\n`LedgerMaster::Stats::collectMetrics`\n\n###### References:\n[Validated ledger](https://xrpl.org/docs/concepts/ledgers/open-closed-validated-ledgers) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#published-ledger)", "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "s", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "thresholds": { + "mode": "absolute", "steps": [ { "color": "green", - "value": null + "value": 0 }, { "color": "yellow", @@ -126,339 +214,131 @@ } ] }, - "custom": {} + "unit": "s" + } + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 12, + "y": 4 + }, + "id": 68, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false }, - "overrides": [] - } - }, - { + "showPercentChange": false, + "textMode": "auto", + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + }, + "wideLayout": true + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(ledgermaster_published_ledger_age{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Published Age\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" + } + ], "title": "Published Ledger Age", - "description": "###### What this is:\n*How old the most recently published ledger is, in seconds.*\n\n###### How it's computed:\n*Current value of the published-ledger-age gauge, refreshed each collection interval.*\n\n###### Reading it:\n*Lower is better; should track close to the validated ledger age.*\n\n###### Healthy range:\n*Under about 7 seconds on a healthy node.*\n\n###### Watch for:\n*A growing gap above the validated age indicates the publish pipeline is backing up.*\n\n###### Source:\n[LedgerMaster.h](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/LedgerMaster.h)\n\n###### Function:\n`LedgerMaster::Stats::collectMetrics`", - "type": "stat", - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 4 + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(ledgermaster_published_ledger_age{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Published Age\", \"\", \"\")" - } - ], + "description": "###### What this is:\n*How often the node requests a ledger from its peers.*\n\n###### How it's computed:\n*Per-second rate of ledger-fetch requests over a 5-minute window.*\n\n###### Reading it:\n*Near zero in steady state; elevated while catching up.*\n\n###### Healthy range:\n*Close to zero once fully synced.*\n\n###### Watch for:\n*A sustained high rate means the node is repeatedly missing ledgers and back-filling from peers.*\n\n###### Keywords:\n- **Ledger acquire (inbound fetch)** *(per node)* \u2014 fetching a specific missing ledger from peers; tracked by duration and outcome (complete/failed).\n- **Back-fill / catch-up** *(per node)* \u2014 fetching missing historical ledgers from peers to fill gaps or reach the network tip.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[InboundLedgers.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/detail/InboundLedgers.cpp)\n\n###### Function:\n`InboundLedgersImp`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#ledger-acquire-inbound-fetch)", "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "s", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "thresholds": { + "mode": "absolute", "steps": [ { "color": "green", - "value": null - }, - { - "color": "yellow", - "value": 10 + "value": 0 }, { "color": "red", - "value": 20 + "value": 80 } ] }, - "custom": {} - }, - "overrides": [] - } - }, - { - "title": "Operating Mode (Time Share)", - "description": "###### What this is:\n*Fraction of recent wall-clock time the node spent in each operating mode (Disconnected, Connected, Syncing, Tracking, Full).*\n\n###### How it's computed:\n*Per-second rate of each per-mode duration counter divided by the sum of all five mode rates, giving each mode's time share.*\n\n###### Reading it:\n*The Full share should sit at or near 1.0 and dominate; other shares should be near 0.*\n\n###### Healthy range:\n*Full share close to 1.0.*\n\n###### Watch for:\n*Share accumulating in Syncing, Connected, or Disconnected means the node is not staying fully synced.*\n\n###### Source:\n[NetworkOPs.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/NetworkOPs.cpp)\n\n###### Function:\n`NetworkOPsImp::Stats`", - "type": "timeseries", + "unit": "suffix: fetches/s" + } + }, "gridPos": { - "h": 8, + "h": 12, "w": 12, "x": 0, - "y": 12 + "y": 16 }, + "id": 69, "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", "tooltip": { "maxHeight": 600, "mode": "multi", "sort": "desc" - } + }, + "wideLayout": true }, + "pluginVersion": "13.2.0-28926505616", "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(state_accounting_full_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) / (rate(state_accounting_disconnected_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_connected_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_syncing_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_tracking_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_full_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])), \"series\", \"Full\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(rate(state_accounting_tracking_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) / (rate(state_accounting_disconnected_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_connected_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_syncing_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_tracking_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_full_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])), \"series\", \"Tracking\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(rate(state_accounting_syncing_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) / (rate(state_accounting_disconnected_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_connected_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_syncing_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_tracking_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_full_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])), \"series\", \"Syncing\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(rate(state_accounting_connected_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) / (rate(state_accounting_disconnected_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_connected_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_syncing_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_tracking_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_full_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])), \"series\", \"Connected\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(rate(state_accounting_disconnected_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) / (rate(state_accounting_disconnected_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_connected_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_syncing_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_tracking_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_full_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])), \"series\", \"Disconnected\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(ledger_fetches_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Fetches / Sec\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" } ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "percentunit", - "min": 0, - "max": 1, - "custom": { - "axisLabel": "Time Share", - "spanNulls": 1800000, - "insertNulls": false, - "showPoints": "auto", - "pointSize": 3 - } - }, - "overrides": [] - } - }, - { - "title": "Operating Mode Transitions", - "description": "###### What this is:\n*Cumulative count of transitions into each operating mode.*\n\n###### How it's computed:\n*Current value of each per-mode transition counter, plotted as lines.*\n\n###### Reading it:\n*Flat lines are healthy; steps up mean the node changed mode.*\n\n###### Healthy range:\n*Few transitions once the node is stable in Full mode.*\n\n###### Watch for:\n*Frequent transitions out of Full, or into Disconnected or Syncing, indicate instability.*\n\n###### Source:\n[NetworkOPs.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/NetworkOPs.cpp)\n\n###### Function:\n`NetworkOPsImp::Stats`", - "type": "timeseries", - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 12 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(state_accounting_full_transitions{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Full\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(state_accounting_tracking_transitions{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Tracking\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(state_accounting_syncing_transitions{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Syncing\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(state_accounting_connected_transitions{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Connected\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(state_accounting_disconnected_transitions{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Disconnected\", \"\", \"\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "short", - "custom": { - "axisLabel": "Transitions", - "spanNulls": 1800000, - "insertNulls": false, - "showPoints": "auto", - "pointSize": 3 - } - }, - "overrides": [] - } - }, - { - "title": "I/O Latency", - "description": "###### What this is:\n*Latency of the I/O service loop, at the 95th percentile in milliseconds.*\n\n###### How it's computed:\n*I/O loop latency samples aggregated to their 95th percentile per node over a 5-minute window.*\n\n###### Reading it:\n*Lower is better; it reflects how promptly queued I/O work runs.*\n\n###### Healthy range:\n*A few milliseconds; brief spikes are tolerable.*\n\n###### Watch for:\n*Sustained values above a few hundred ms indicate thread-pool saturation or blocking operations.*\n\n###### Source:\n[Application.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/main/Application.cpp)\n\n###### Function:\n`ApplicationImp`", - "type": "timeseries", - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 20 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(histogram_quantile(0.95, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(ios_latency_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"P95 I/O Latency\", \"\", \"\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "ms", - "custom": { - "axisLabel": "Latency (ms)", - "spanNulls": 1800000, - "insertNulls": false, - "showPoints": "auto", - "pointSize": 3 - } - }, - "overrides": [] - } - }, - { - "title": "Job Queue Depth", - "description": "###### What this is:\n*Number of jobs currently waiting in the internal work queue.*\n\n###### How it's computed:\n*Current value of the job-count gauge, plotted over time.*\n\n###### Reading it:\n*Lower is better; a near-flat low line means the node keeps up with its workload.*\n\n###### Healthy range:\n*Low single digits at idle, brief bumps under load.*\n\n###### Watch for:\n*A sustained high depth means the node cannot process work fast enough, common during replay, heavy RPC, or a request flood.*\n\n###### Source:\n[JobQueue.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/core/detail/JobQueue.cpp)\n\n###### Function:\n`JobQueue`", - "type": "timeseries", - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 20 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(job_count{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Job Queue Depth\", \"\", \"\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "short", - "custom": { - "axisLabel": "Jobs", - "spanNulls": 1800000, - "insertNulls": false, - "showPoints": "auto", - "pointSize": 3 - } - }, - "overrides": [] - } - }, - { "title": "Ledger Fetch Rate", - "description": "###### What this is:\n*How often the node requests a ledger from its peers.*\n\n###### How it's computed:\n*Per-second rate of ledger-fetch requests over a 5-minute window.*\n\n###### Reading it:\n*Near zero in steady state; elevated while catching up.*\n\n###### Healthy range:\n*Close to zero once fully synced.*\n\n###### Watch for:\n*A sustained high rate means the node is repeatedly missing ledgers and back-filling from peers.*\n\n###### Source:\n[InboundLedgers.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/detail/InboundLedgers.cpp)\n\n###### Function:\n`InboundLedgersImp`", - "type": "stat", - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 28 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(rate(ledger_fetches_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Fetches / Sec\", \"\", \"\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "suffix: fetches/s", - "custom": {} - }, - "overrides": [] - } + "type": "stat" }, { - "title": "Ledger History Mismatches", - "description": "###### What this is:\n*Rate at which a locally built ledger hash fails to match the network-validated hash.*\n\n###### How it's computed:\n*Per-second rate of history-mismatch events over a 5-minute window.*\n\n###### Reading it:\n*Flat at zero is the only healthy reading.*\n\n###### Healthy range:\n*Zero.*\n\n###### Watch for:\n*Any nonzero value indicates consensus divergence or database corruption and warrants immediate investigation.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`incrementLedgerHistoryMismatch`", - "type": "stat", - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 28 + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(rate(ledger_history_mismatch_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Mismatches / Sec\", \"\", \"\")" - } - ], + "description": "###### What this is:\n*Rate at which a locally built ledger hash fails to match the network-validated hash.*\n\n###### How it's computed:\n*Per-second rate of history-mismatch events over a 5-minute window.*\n\n###### Reading it:\n*Flat at zero is the only healthy reading.*\n\n###### Healthy range:\n*Zero.*\n\n###### Watch for:\n*Any nonzero value indicates consensus divergence or database corruption and warrants immediate investigation.*\n\n###### Keywords:\n- **Ledger history mismatch** *(per node)* \u2014 when a locally built ledger's hash does not match the network-validated hash.\n- **Validated ledger** *(network-wide)* \u2014 a ledger confirmed final by the trusted validator quorum; its contents never change.\n- **Consensus** *(network event)* \u2014 the protocol by which validators agree on the next ledger's transaction set and close time.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`incrementLedgerHistoryMismatch`\n\n###### References:\n[Validated ledger](https://xrpl.org/docs/concepts/ledgers/open-closed-validated-ledgers) \u00b7 [Consensus](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#ledger-history-mismatch)", "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "suffix: mismatches/s", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "thresholds": { + "mode": "absolute", "steps": [ { "color": "green", - "value": null + "value": 0 }, { "color": "red", @@ -466,1663 +346,65 @@ } ] }, - "custom": {} - }, - "overrides": [] - } - }, - { - "title": "NodeStore I/O", - "type": "row", - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 36 + "unit": "suffix: mismatches/s" + } }, - "collapsed": false, - "panels": [] - }, - { - "title": "NodeStore Throughput", - "description": "###### What this is:\n*Object-store read, found and write operation rates. The found series is `node_reads_hit`, which counts fetches that returned an object whatever served them, so it is not a cache-hit count.*\n\n###### How it's computed:\n*Per-second rates of the read, found and write counters, plotted as lines.*\n\n###### Reading it:\n*Steadily rising lines are normal; the slope reflects store activity.*\n\n###### Healthy range:\n*Reads and writes rising smoothly, with found tracking almost all reads on a node that has the data.*\n\n###### Watch for:\n*A sudden surge in reads or writes signals heavy back-end I/O, from sync, replay, or query load.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerNodeStoreGauge`", - "type": "timeseries", "gridPos": { - "x": 0, - "y": 37, + "h": 12, "w": 12, - "h": 8 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(rate(nodestore_state{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"node_reads_total\"}[$__rate_interval]), \"series\", \"Reads Total\", \"\", \"\")", - "refId": "A" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(rate(nodestore_state{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"node_reads_hit\"}[$__rate_interval]), \"series\", \"Reads Found\", \"\", \"\")", - "refId": "B" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(rate(nodestore_state{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"node_writes\"}[$__rate_interval]), \"series\", \"Writes Total\", \"\", \"\")", - "refId": "C" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "suffix: operations/s", - "custom": { - "axisLabel": "Operations / Sec", - "drawStyle": "line", - "lineWidth": 2, - "fillOpacity": 10, - "spanNulls": 1800000 - }, - "color": { - "mode": "palette-classic" - } - }, - "overrides": [] - } - }, - { - "title": "NodeStore Read Found Ratio", - "description": "###### What this is:\n*Fraction of object-store fetches that found the object they asked for. This is not a cache hit ratio: `node_reads_hit` counts every fetch that returned an object, whatever served it, so a fetch that went all the way to disk still counts here.*\n\n###### How it's computed:\n*Rate of node_reads_hit divided by rate of node_reads_total over the panel interval.*\n\n###### Reading it:\n*Normally sits near 1.0 (100%) on any node that has the data, warm or cold. It does not fall when the page cache goes cold.*\n\n###### Healthy range:\n*Near 1.0. A ratio well below 1.0 means fetches are missing, which points at a gap in local history rather than at cache pressure.*\n\n###### Watch for:\n*Never read this panel on its own. A ~100% found ratio at over 100 microseconds per read is the cold-read signature, not a healthy cache: the data is found every time and paid for every time. Pair it with read latency.*\n\n###### Source:\n[nodestore/Database.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/nodestore/Database.cpp)\n\n###### Function:\n`Database::fetchNodeObject`", - "type": "timeseries", - "gridPos": { "x": 12, - "y": 37, - "w": 12, - "h": 8 + "y": 16 }, + "id": 70, "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", "tooltip": { "maxHeight": 600, "mode": "multi", "sort": "desc" - } + }, + "wideLayout": true }, + "pluginVersion": "13.2.0-28926505616", "targets": [ { "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(nodestore_state{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"node_reads_hit\"}[$__rate_interval]) / ignoring(metric) rate(nodestore_state{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"node_reads_total\"}[$__rate_interval]), \"series\", \"Found Ratio\", \"\", \"\")", + "expr": "label_replace(label_join(label_replace(rate(ledger_history_mismatch_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Mismatches / Sec\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", "refId": "A" } ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "percentunit", - "custom": { - "axisLabel": "Found Ratio", - "drawStyle": "line", - "lineWidth": 2, - "fillOpacity": 10, - "spanNulls": 1800000 - }, - "color": { - "mode": "palette-classic" - } - }, - "overrides": [] - } + "title": "Ledger History Mismatches", + "type": "stat" }, { - "title": "NodeStore Write Load & Read Queue", - "description": "###### What this is:\n*Instantaneous write-load score and read-queue depth of the object store.*\n\n###### How it's computed:\n*Current values of the write-load and read-queue gauges, plotted over time.*\n\n###### Reading it:\n*Lower is better for both; short, flat lines are healthy.*\n\n###### Healthy range:\n*Write load near zero and read queue in low double digits or less.*\n\n###### Watch for:\n*High write load means back-end pressure; a high read queue means the prefetch threads are saturated.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerNodeStoreGauge`", - "type": "timeseries", - "gridPos": { - "x": 0, - "y": 45, - "w": 12, - "h": 8 + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(nodestore_state{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"write_load\"}, \"series\", \"Write Load\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(nodestore_state{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"read_queue\"}, \"series\", \"Read Queue\", \"\", \"\")" - } - ], + "description": "###### What this is:\n*The lag between published and validated ledger ages, in seconds.*\n\n###### How it's computed:\n*Published ledger age minus validated ledger age, as a single derived value.*\n\n###### Reading it:\n*Near zero is healthy; a positive value is how far publishing trails validation.*\n\n###### Healthy range:\n*Close to zero.*\n\n###### Watch for:\n*A growing gap means the publish pipeline is falling behind and subscribers may see stale data.*\n\n###### Keywords:\n- **Validated ledger** *(network-wide)* \u2014 a ledger confirmed final by the trusted validator quorum; its contents never change.\n- **Published ledger** *(per node)* \u2014 the most recent validated ledger the node has finished publishing to its subscribers.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[LedgerMaster.h](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/LedgerMaster.h)\n\n###### Function:\n`LedgerMaster::Stats::collectMetrics`\n\n###### References:\n[Validated ledger](https://xrpl.org/docs/concepts/ledgers/open-closed-validated-ledgers) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#validated-ledger)", "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "short", - "custom": { - "axisLabel": "Count", - "drawStyle": "line", - "lineWidth": 2, - "fillOpacity": 10, - "spanNulls": 1800000 - }, - "color": { - "mode": "palette-classic" - }, + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "thresholds": { + "mode": "absolute", "steps": [ { "color": "green", - "value": null - }, - { - "color": "yellow", - "value": 100 - }, - { - "color": "red", - "value": 1000 - } - ] - } - }, - "overrides": [] - } - }, - { - "title": "NodeStore Bytes Read/Written", - "description": "###### What this is:\n*Cumulative bytes read from and written to the object-store back end.*\n\n###### How it's computed:\n*Current values of the bytes-read and bytes-written counters, plotted as lines.*\n\n###### Reading it:\n*Steadily rising lines are normal; the slope shows throughput.*\n\n###### Healthy range:\n*Smooth growth consistent with ledger and query activity.*\n\n###### Watch for:\n*A sharp acceleration indicates a heavy I/O phase such as sync, replay, or large queries.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerNodeStoreGauge`", - "type": "timeseries", - "gridPos": { - "x": 12, - "y": 45, - "w": 12, - "h": 8 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(rate(nodestore_state{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"node_read_bytes\"}[$__rate_interval]), \"series\", \"Bytes Read\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(rate(nodestore_state{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"node_written_bytes\"}[$__rate_interval]), \"series\", \"Bytes Written\", \"\", \"\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "Bps", - "custom": { - "axisLabel": "Throughput", - "drawStyle": "line", - "lineWidth": 2, - "fillOpacity": 10, - "spanNulls": 1800000 - }, - "color": { - "mode": "palette-classic" - } - }, - "overrides": [] - } - }, - { - "title": "NodeStore Read Threads", - "description": "###### What this is:\n*Read-thread utilization, bundled read count, and cumulative read time for the object store.*\n\n###### How it's computed:\n*Current values of the running/total read-thread gauges, read-bundle gauge, and cumulative read-duration counter, plotted as lines.*\n\n###### Reading it:\n*Running threads well below total means spare capacity; a rising duration line reflects time spent in read I/O.*\n\n###### Healthy range:\n*Running threads below the total count most of the time.*\n\n###### Watch for:\n*Running threads pinned at the total for long periods means read I/O is saturated.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerNodeStoreGauge`", - "type": "timeseries", - "gridPos": { - "x": 0, - "y": 53, - "w": 12, - "h": 8 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(nodestore_state{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"read_threads_running\"}, \"series\", \"Read Threads Running\", \"\", \"\")", - "refId": "A" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(nodestore_state{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"read_threads_total\"}, \"series\", \"Read Threads Total\", \"\", \"\")", - "refId": "B" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(nodestore_state{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"read_request_bundle\"}, \"series\", \"Read Request Bundle\", \"\", \"\")", - "refId": "C" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "short", - "custom": { - "axisLabel": "Threads / Bundle", - "drawStyle": "line", - "lineWidth": 2, - "fillOpacity": 10, - "spanNulls": 1800000 - }, - "color": { - "mode": "palette-classic" - } - }, - "overrides": [] - } - }, - { - "title": "NodeStore Read Busy Ratio", - "description": "###### What this is:\n*Read-thread utilization, bundled read count, and cumulative read time for the object store.*\n\n###### How it's computed:\n*Current values of the running/total read-thread gauges, read-bundle gauge, and cumulative read-duration counter, plotted as lines.*\n\n###### Reading it:\n*Running threads well below total means spare capacity; a rising duration line reflects time spent in read I/O.*\n\n###### Healthy range:\n*Running threads below the total count most of the time.*\n\n###### Watch for:\n*Running threads pinned at the total for long periods means read I/O is saturated.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerNodeStoreGauge`", - "type": "timeseries", - "gridPos": { - "x": 12, - "y": 53, - "w": 12, - "h": 8 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(rate(nodestore_state{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"node_reads_duration_us\"}[$__rate_interval]) / 1e6, \"series\", \"Read Busy Ratio\", \"\", \"\")", - "refId": "A" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "percentunit", - "custom": { - "axisLabel": "Threads / Bundle", - "drawStyle": "line", - "lineWidth": 2, - "fillOpacity": 10, - "spanNulls": 1800000 - }, - "color": { - "mode": "palette-classic" - } - }, - "overrides": [] - } - }, - { - "title": "Caches", - "type": "row", - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 61 - }, - "collapsed": false, - "panels": [] - }, - { - "title": "Cache Hit Rates", - "description": "###### What this is:\n*Hit rates for the SLE, Ledger, and AcceptedLedger caches, from 0 to 1.*\n\n###### How it's computed:\n*Current values of the per-cache hit-rate gauges, plotted as lines.*\n\n###### Reading it:\n*Higher is better; each line is the fraction of lookups served from cache.*\n\n###### Healthy range:\n*Above roughly 0.8 in steady state.*\n\n###### Watch for:\n*Low or falling hit rates indicate cache thrashing and extra back-end reads.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerCacheHitRateGauge`", - "type": "timeseries", - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 62 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(cache_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"SLE_hit_rate\"}, \"series\", \"SLE Hit Rate\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(cache_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"ledger_hit_rate\"}, \"series\", \"Ledger Hit Rate\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(cache_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"AL_hit_rate\"}, \"series\", \"AcceptedLedger Hit Rate\", \"\", \"\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "percentunit", - "min": 0, - "max": 1, - "custom": { - "drawStyle": "line", - "lineWidth": 2, - "fillOpacity": 10, - "axisLabel": "Hit Rate", - "spanNulls": 1800000, - "insertNulls": false, - "showPoints": "auto", - "pointSize": 3 - }, - "color": { - "mode": "palette-classic" - } - }, - "overrides": [] - } - }, - { - "title": "Cache Sizes", - "description": "###### What this is:\n*Entry counts for the TreeNode cache and track set, the FullBelow cache, and the AcceptedLedger cache.*\n\n###### How it's computed:\n*Current values of the per-cache size gauges, plotted as lines.*\n\n###### Reading it:\n*Stable lines are normal; sizes grow with working set and shrink after sweeps.*\n\n###### Healthy range:\n*Stable within configured limits.*\n\n###### Watch for:\n*Unbounded growth suggests memory pressure or a cache not being swept.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerCacheHitRateGauge`", - "type": "timeseries", - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 62 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(cache_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"treenode_cache_size\"}, \"series\", \"TreeNode Cache\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(cache_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"treenode_track_size\"}, \"series\", \"TreeNode Track\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(cache_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"fullbelow_size\"}, \"series\", \"FullBelow\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(cache_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"AL_size\"}, \"series\", \"AcceptedLedger Size\", \"\", \"\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "short", - "custom": { - "axisLabel": "Entries", - "drawStyle": "line", - "lineWidth": 2, - "fillOpacity": 10, - "spanNulls": 1800000 - }, - "color": { - "mode": "palette-classic" - } - }, - "overrides": [] - } - }, - { - "title": "Object Instances", - "type": "row", - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 70 - }, - "collapsed": false, - "panels": [] - }, - { - "title": "Object Instance Counts", - "description": "###### What this is:\n*Live instance counts for the busiest internal object types.*\n\n###### How it's computed:\n*Current per-type instance counts, showing the top 15 types over time.*\n\n###### Reading it:\n*Stable lines are healthy; each line is one object type's live count.*\n\n###### Healthy range:\n*Steady counts that rise and fall with load.*\n\n###### Watch for:\n*A single type climbing without bound suggests memory pressure or a leak.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerObjectCountGauge`", - "type": "timeseries", - "gridPos": { - "h": 8, - "w": 24, - "x": 0, - "y": 71 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - }, - "legend": { - "displayMode": "table", - "placement": "right", - "calcs": ["last", "max"] - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(topk(15, object_count{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", type=~\"$type\"}), \"series\", \"$1\", \"type\", \"(.*)\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "short", - "custom": { - "axisLabel": "Instances", - "drawStyle": "line", - "lineWidth": 1, - "fillOpacity": 5, - "spanNulls": 1800000 - }, - "color": { - "mode": "palette-classic" - } - }, - "overrides": [] - } - }, - { - "title": "Server Info", - "type": "row", - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 79 - }, - "collapsed": false, - "panels": [] - }, - { - "title": "Server State", - "description": "###### What this is:\n*The node's current operating mode, shown as a labeled state.*\n\n###### How it's computed:\n*Current value of the server-state gauge mapped to a mode name.*\n\n###### Reading it:\n*FULL (green) is the goal; lower states mean the node is not fully participating.*\n\n###### Healthy range:\n*FULL.*\n\n###### Watch for:\n*Sitting in SYNCING, CONNECTED, or DISCONNECTED means the node is not caught up to the network.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerServerInfoGauge`", - "type": "stat", - "gridPos": { - "h": 8, - "w": 6, - "x": 0, - "y": 80 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(server_info{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"server_state\"}, \"series\", \"State\", \"\", \"\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "none", - "mappings": [ - { - "type": "value", - "options": { - "0": { - "text": "DISCONNECTED", - "color": "red" - } - } - }, - { - "type": "value", - "options": { - "1": { - "text": "CONNECTED", - "color": "orange" - } - } - }, - { - "type": "value", - "options": { - "2": { - "text": "SYNCING", - "color": "yellow" - } - } - }, - { - "type": "value", - "options": { - "3": { - "text": "TRACKING", - "color": "blue" - } - } - }, - { - "type": "value", - "options": { - "4": { - "text": "FULL", - "color": "green" - } - } - } - ], - "custom": {} - }, - "overrides": [] - } - }, - { - "title": "Uptime", - "description": "###### What this is:\n*How long the server process has been running, in seconds.*\n\n###### How it's computed:\n*Current value of the uptime gauge.*\n\n###### Reading it:\n*Higher is generally better; a reset to a small value means the process restarted.*\n\n###### Healthy range:\n*Continuously increasing.*\n\n###### Watch for:\n*An unexpected drop to near zero indicates a restart or crash.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerServerInfoGauge`", - "type": "stat", - "gridPos": { - "h": 8, - "w": 6, - "x": 6, - "y": 80 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(server_info{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"uptime\"}, \"series\", \"Uptime\", \"\", \"\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "s", - "custom": {} - }, - "overrides": [] - } - }, - { - "title": "Peer Count", - "description": "###### What this is:\n*Total connected peers, inbound plus outbound.*\n\n###### How it's computed:\n*Current value of the peer-count gauge.*\n\n###### Reading it:\n*A stable count in the healthy range is good; too few limits connectivity.*\n\n###### Healthy range:\n*Workload- and config-dependent, typically 10 or more.*\n\n###### Watch for:\n*A sudden drop points to network or connectivity problems; an unusually high inbound count can indicate connection-flood pressure.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerServerInfoGauge`", - "type": "stat", - "gridPos": { - "h": 8, - "w": 6, - "x": 12, - "y": 80 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(server_info{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"peers\"}, \"series\", \"Peers\", \"\", \"\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "short", - "custom": {} - }, - "overrides": [] - } - }, - { - "title": "Validated Ledger Seq", - "description": "###### What this is:\n*Sequence number of the most recently validated ledger.*\n\n###### How it's computed:\n*Current value of the validated-ledger-sequence gauge.*\n\n###### Reading it:\n*Should climb steadily; reads 0 before the first validation.*\n\n###### Healthy range:\n*Continuously increasing at the network close cadence.*\n\n###### Watch for:\n*A flat sequence means the node has stopped validating new ledgers.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerServerInfoGauge`", - "type": "stat", - "gridPos": { - "h": 8, - "w": 6, - "x": 18, - "y": 80 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(server_info{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"validated_ledger_seq\"}, \"series\", \"Seq\", \"\", \"\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "none", - "custom": {} - }, - "overrides": [] - } - }, - { - "title": "Last Close \u2014 Proposers", - "description": "###### What this is:\n*Proposer count and convergence time from the last closed consensus round.*\n\n###### How it's computed:\n*Current values of the last-close proposer-count and convergence-time gauges, plotted as lines.*\n\n###### Reading it:\n*A healthy proposer count with a low convergence time is good.*\n\n###### Healthy range:\n*Convergence time of a few seconds with the expected number of proposers.*\n\n###### Watch for:\n*A falling proposer count or rising convergence time signals degrading consensus conditions.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerServerInfoGauge`", - "type": "timeseries", - "gridPos": { - "x": 0, - "y": 88, - "w": 12, - "h": 8 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(server_info{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"last_close_proposers\"}, \"series\", \"Proposers\", \"\", \"\")", - "refId": "A" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "short", - "custom": { - "axisLabel": "Proposers", - "drawStyle": "line", - "lineWidth": 2, - "fillOpacity": 10, - "spanNulls": 1800000 - }, - "color": { - "mode": "palette-classic" - } - }, - "overrides": [] - } - }, - { - "title": "Last Close \u2014 Converge Time", - "description": "###### What this is:\n*Proposer count and convergence time from the last closed consensus round.*\n\n###### How it's computed:\n*Current values of the last-close proposer-count and convergence-time gauges, plotted as lines.*\n\n###### Reading it:\n*A healthy proposer count with a low convergence time is good.*\n\n###### Healthy range:\n*Convergence time of a few seconds with the expected number of proposers.*\n\n###### Watch for:\n*A falling proposer count or rising convergence time signals degrading consensus conditions.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerServerInfoGauge`", - "type": "timeseries", - "gridPos": { - "x": 12, - "y": 88, - "w": 12, - "h": 8 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(server_info{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"last_close_converge_time_ms\"}, \"series\", \"Converge Time ms\", \"\", \"\")", - "refId": "A" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "ms", - "custom": { - "axisLabel": "Proposers", - "drawStyle": "line", - "lineWidth": 2, - "fillOpacity": 10, - "spanNulls": 1800000 - }, - "color": { - "mode": "palette-classic" - } - }, - "overrides": [] - } - }, - { - "title": "Build Version", - "description": "###### What this is:\n*The running server's build version string.*\n\n###### How it's computed:\n*Read from the version label of the build-info metric (its value is always 1).*\n\n###### Reading it:\n*Confirms which version each node is running.*\n\n###### Healthy range:\n*The expected release version across all nodes.*\n\n###### Watch for:\n*A node on an unexpected or mismatched version in a fleet.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerBuildInfoGauge`", - "type": "stat", - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 96 - }, - "options": { - "tooltip": { - "mode": "multi", - "sort": "desc" - }, - "textMode": "name" - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "build_info{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}", - "legendFormat": "v{{version}} [{{service_instance_id}}]" - } - ], - "fieldConfig": { - "defaults": { - "unit": "none", - "custom": {} - }, - "overrides": [] - } - }, - { - "title": "Current Ledger Index", - "description": "###### What this is:\n*Sequence number of the current open ledger.*\n\n###### How it's computed:\n*Current value of the open-ledger index gauge.*\n\n###### Reading it:\n*Should climb steadily; the gap above the validated sequence is the ledgers in flight.*\n\n###### Healthy range:\n*One or two ahead of the validated sequence.*\n\n###### Watch for:\n*A large or growing gap above the validated sequence means validation is lagging behind ledger creation.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerServerInfoGauge`", - "type": "stat", - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 96 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(server_info{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"ledger_current_index\"}, \"series\", \"Current Ledger\", \"\", \"\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "none", - "custom": {} - }, - "overrides": [] - } - }, - { - "title": "Stored Object Bytes", - "description": "###### What this is:\n*Cumulative object-payload bytes this process has written to the NodeStore object-store back end. This is not the size of the store on disk.*\n\n###### How it's computed:\n*Current value of the stored_object_bytes gauge, plotted over time. It observes getStoreSize(), the same accessor node_written_bytes uses, so the two series are equal and their ratio is a constant 1.0 rather than a write-amplification measure.*\n\n###### Reading it:\n*A smoothly growing line is normal; the slope is the write rate. It excludes NuDB's keys, bucket padding and log, and it restarts from zero with the process while the files on disk do not.*\n\n###### Healthy range:\n*Gradual growth consistent with ledger data being stored.*\n\n###### Watch for:\n*A sudden jump in growth rate can indicate runaway storage or an unexpected back-fill. Do not use this panel to size the store on disk or to plan disk capacity; no metric reports on-disk size today, so check the filesystem directly.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerStorageDetailGauge`", - "type": "timeseries", - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 104 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(storage_detail{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"stored_object_bytes\"}, \"series\", \"Stored Object Bytes\", \"\", \"\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "decbytes", - "custom": { - "axisLabel": "Stored Bytes", - "drawStyle": "line", - "lineWidth": 2, - "fillOpacity": 10, - "spanNulls": 1800000 - }, - "color": { - "mode": "palette-classic" - } - }, - "overrides": [] - } - }, - { - "title": "Complete Ledgers & DB", - "type": "row", - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 112 - }, - "collapsed": false, - "panels": [] - }, - { - "title": "Complete Ledger Ranges", - "description": "###### What this is:\n*The contiguous ranges of ledgers the node holds locally.*\n\n###### How it's computed:\n*Current start and end bounds of each complete range, listed as table rows.*\n\n###### Reading it:\n*Fewer ranges is better; one continuous range means an unbroken history.*\n\n###### Healthy range:\n*A single range covering the configured retention window.*\n\n###### Watch for:\n*Many fragmented ranges indicate gaps in stored history from missed or failed fetches.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerCompleteLedgersGauge`", - "type": "table", - "gridPos": { - "h": 8, - "w": 24, - "x": 0, - "y": 113 - }, - "options": { - "showHeader": true - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "complete_ledgers{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}", - "legendFormat": "{{bound}} [range {{index}}] [{{service_instance_id}}]", - "format": "table", - "instant": true - } - ], - "fieldConfig": { - "defaults": { - "unit": "none", - "custom": {} - }, - "overrides": [] - } - }, - { - "title": "Database Sizes", - "description": "###### What this is:\n*Sizes of the relational databases in KB (total, ledger, transaction).*\n\n###### How it's computed:\n*Current values of the per-database size gauges, plotted as lines.*\n\n###### Reading it:\n*Smoothly growing lines are normal; the split shows where storage is used.*\n\n###### Healthy range:\n*Gradual growth consistent with retained history.*\n\n###### Watch for:\n*An abrupt change in growth rate can indicate storage pressure or a pruning issue.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerDbMetricsGauge`", - "type": "timeseries", - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 121 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(db_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"db_kb_total\"}, \"series\", \"Total KB\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(db_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"db_kb_ledger\"}, \"series\", \"Ledger KB\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(db_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"db_kb_transaction\"}, \"series\", \"Transaction KB\", \"\", \"\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "deckbytes", - "custom": { - "axisLabel": "Size (KB)", - "drawStyle": "line", - "lineWidth": 2, - "fillOpacity": 10, - "spanNulls": 1800000 - }, - "color": { - "mode": "palette-classic" - } - }, - "overrides": [] - } - }, - { - "title": "Historical Fetch Rate", - "description": "###### What this is:\n*How many historical ledgers the node is back-filling per minute.*\n\n###### How it's computed:\n*Current value of the historical-fetch-per-minute gauge.*\n\n###### Reading it:\n*Near zero once history is complete; elevated while back-filling.*\n\n###### Healthy range:\n*Close to zero in steady state.*\n\n###### Watch for:\n*A sustained high rate means the node is still filling gaps in its stored history.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerDbMetricsGauge`", - "type": "stat", - "gridPos": { - "h": 8, - "w": 24, - "x": 0, - "y": 129 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(db_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"historical_perminute\"}, \"series\", \"Fetches/min\", \"\", \"\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "suffix: fetches/min", - "custom": {} - }, - "overrides": [] - } - }, - { - "title": "Peer Disconnects (Resources)", - "description": "###### What this is:\n*Cumulative count of peers disconnected for exceeding resource limits.*\n\n###### How it's computed:\n*Current value of the resource-disconnect gauge, plotted over time.*\n\n###### Reading it:\n*A flat line is healthy; steps up mean peers were dropped for overuse.*\n\n###### Healthy range:\n*Flat or very slowly rising.*\n\n###### Watch for:\n*A rising line indicates peers are being throttled off, consistent with abusive or misbehaving peers.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerServerInfoGauge`", - "type": "timeseries", - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 137 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(server_info{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"peer_disconnects_resources\"}, \"series\", \"Resource Disconnects\", \"\", \"\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "short", - "custom": { - "drawStyle": "line", - "lineWidth": 2, - "fillOpacity": 10, - "spanNulls": 1800000 - }, - "color": { - "mode": "palette-classic" - } - }, - "overrides": [] - } - }, - { - "title": "Ledger Economy", - "type": "row", - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 145 - }, - "collapsed": false, - "panels": [] - }, - { - "title": "Base Fee (drops)", - "description": "###### What this is:\n*The node's local load-based fee factor that scales its minimum transaction cost; baseline 256 at idle.*\n\n###### How it's computed:\n*Current value of the local load-fee economy gauge.*\n\n###### Reading it:\n*Steady at the baseline (256) is normal; higher values mean the node is raising its fee in response to load.*\n\n###### Healthy range:\n*Around 256 (the normal baseline) when idle.*\n\n###### Watch for:\n*A climbing factor, which indicates the node is under transaction load pressure.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerLedgerEconomyGauge`", - "type": "stat", - "gridPos": { - "h": 8, - "w": 6, - "x": 0, - "y": 146 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(ledger_economy{metric=\"base_fee_xrp\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Base Fee\", \"\", \"\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "si:drops", - "custom": {} - }, - "overrides": [] - } - }, - { - "title": "Reserve Base (drops)", - "description": "###### What this is:\n*The minimum XRP balance required to keep an account on the ledger, in drops.*\n\n###### How it's computed:\n*Current value of the reserve-base economy gauge.*\n\n###### Reading it:\n*A stable value is expected; it changes only via network amendment or vote.*\n\n###### Healthy range:\n*The configured network reserve base.*\n\n###### Watch for:\n*An unexpected change outside a known amendment or fee vote.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerLedgerEconomyGauge`", - "type": "stat", - "gridPos": { - "h": 8, - "w": 6, - "x": 6, - "y": 146 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(ledger_economy{metric=\"reserve_base_xrp\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Reserve Base\", \"\", \"\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "si:drops", - "custom": {} - }, - "overrides": [] - } - }, - { - "title": "Reserve Inc (drops)", - "description": "###### What this is:\n*The additional XRP reserve required per owned ledger object, in drops.*\n\n###### How it's computed:\n*Current value of the reserve-increment economy gauge.*\n\n###### Reading it:\n*A stable value is expected; it changes only via network amendment or vote.*\n\n###### Healthy range:\n*The configured network owner reserve increment.*\n\n###### Watch for:\n*An unexpected change outside a known amendment or fee vote.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerLedgerEconomyGauge`", - "type": "stat", - "gridPos": { - "h": 8, - "w": 6, - "x": 12, - "y": 146 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(ledger_economy{metric=\"reserve_inc_xrp\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Reserve Inc\", \"\", \"\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "si:drops", - "custom": {} - }, - "overrides": [] - } - }, - { - "title": "Ledger Age", - "description": "###### What this is:\n*Seconds since the last validated ledger closed, plotted over time.*\n\n###### How it's computed:\n*Current value of the ledger-age economy gauge, sampled each interval.*\n\n###### Reading it:\n*Lower is better; it should stay near the ledger close interval. Mirrors the Validated Ledger Age panel.*\n\n###### Healthy range:\n*Under about 10 seconds.*\n\n###### Watch for:\n*Growth beyond the expected close interval, meaning the node is not keeping up with validated ledgers.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerLedgerEconomyGauge`", - "type": "timeseries", - "gridPos": { - "h": 8, - "w": 6, - "x": 18, - "y": 146 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(ledger_economy{metric=\"ledger_age_seconds\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Ledger Age\", \"\", \"\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "s", - "thresholds": { - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "yellow", - "value": 10 - }, - { - "color": "red", - "value": 20 - } - ] - }, - "custom": { - "axisLabel": "Ledger Age (s)", - "drawStyle": "line", - "lineWidth": 2, - "fillOpacity": 10, - "spanNulls": 1800000, - "insertNulls": false, - "showPoints": "auto", - "pointSize": 3, - "thresholdsStyle": { - "mode": "line" - } - } - }, - "overrides": [] - } - }, - { - "title": "Transaction Rate", - "description": "###### What this is:\n*The network transaction throughput reported by the ledger economy metrics.*\n\n###### How it's computed:\n*Current value of the transaction-rate economy gauge, plotted over time.*\n\n###### Reading it:\n*Reflects how many transactions are being processed; higher means busier.*\n\n###### Healthy range:\n*Workload-dependent.*\n\n###### Watch for:\n*A sudden sustained surge can indicate a transaction flood; a drop to zero can indicate the node stopped processing.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerLedgerEconomyGauge`", - "type": "timeseries", - "gridPos": { - "h": 8, - "w": 24, - "x": 0, - "y": 154 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(ledger_economy{metric=\"transaction_rate\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Tx Rate\", \"\", \"\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "suffix: transactions/s", - "custom": { - "axisLabel": "Transactions / Sec", - "drawStyle": "line", - "lineWidth": 2, - "fillOpacity": 10, - "spanNulls": 1800000, - "insertNulls": false, - "showPoints": "auto", - "pointSize": 3 - }, - "color": { - "mode": "palette-classic" - } - }, - "overrides": [] - } - }, - { - "title": "Extended Metrics", - "type": "row", - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 162 - }, - "collapsed": false, - "panels": [] - }, - { - "title": "Key Jobs Execution Time (q$quantile)", - "description": "###### What this is:\n*Execution time for the most critical job types at the selected quantile.*\n\n###### How it's computed:\n*Per-job-type execution durations aggregated to the chosen quantile per node over a 5-minute window.*\n\n###### Reading it:\n*Lower is better; each line is one job type's run time.*\n\n###### Healthy range:\n*A few to tens of milliseconds for most jobs; workload-dependent.*\n\n###### Watch for:\n*Spikes on key jobs (accept, transaction, write) indicate processing bottlenecks.*\n\n###### Source:\n[JobTypeData.h](https://github.com/XRPLF/rippled/blob/develop/include/xrpl/core/JobTypeData.h)\n\n###### Function:\n`JobTypeData`", - "type": "timeseries", - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 163 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_running_us_bucket{job_type=\"acceptLedger\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Accept Ledger\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_running_us_bucket{job_type=\"advanceLedger\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Advance Ledger\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_running_us_bucket{job_type=\"transaction\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Transaction\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_running_us_bucket{job_type=\"writeObjects\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Write Objects\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_running_us_bucket{job_type=\"heartbeat\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Heartbeat\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_running_us_bucket{job_type=\"sweep\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Sweep\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_running_us_bucket{job_type=\"trustedValidation\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Trusted Validation\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_running_us_bucket{job_type=\"trustedProposal\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Trusted Proposal\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_running_us_bucket{job_type=\"publishNewLedger\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Publish New Ledger\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_running_us_bucket{job_type=\"clientRPC\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Client RPC\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_running_us_bucket{job_type=\"ledgerData\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Ledger Data\", \"\", \"\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "\u00b5s", - "custom": { - "axisLabel": "Duration (\u00b5s)", - "spanNulls": 1800000, - "insertNulls": false, - "showPoints": "auto", - "pointSize": 3 - } - }, - "overrides": [] - } - }, - { - "title": "Key Jobs Dequeue Wait Time (q$quantile)", - "description": "###### What this is:\n*Time critical jobs wait in the queue before running, at the selected quantile.*\n\n###### How it's computed:\n*Per-job-type queue-wait durations aggregated to the chosen quantile per node over a 5-minute window.*\n\n###### Reading it:\n*Lower is better; each line is one job type's wait time.*\n\n###### Healthy range:\n*Near zero to a few milliseconds when the queue is keeping up.*\n\n###### Watch for:\n*High waits mean the queue is backlogged and jobs are scheduled late.*\n\n###### Source:\n[JobTypeData.h](https://github.com/XRPLF/rippled/blob/develop/include/xrpl/core/JobTypeData.h)\n\n###### Function:\n`JobTypeData`", - "type": "timeseries", - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 163 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_queued_us_bucket{job_type=\"acceptLedger\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Accept Ledger\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_queued_us_bucket{job_type=\"advanceLedger\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Advance Ledger\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_queued_us_bucket{job_type=\"transaction\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Transaction\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_queued_us_bucket{job_type=\"writeObjects\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Write Objects\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_queued_us_bucket{job_type=\"heartbeat\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Heartbeat\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_queued_us_bucket{job_type=\"sweep\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Sweep\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_queued_us_bucket{job_type=\"trustedValidation\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Trusted Validation\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_queued_us_bucket{job_type=\"trustedProposal\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Trusted Proposal\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_queued_us_bucket{job_type=\"publishNewLedger\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Publish New Ledger\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_queued_us_bucket{job_type=\"clientRPC\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Client RPC\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_queued_us_bucket{job_type=\"ledgerData\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Ledger Data\", \"\", \"\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "\u00b5s", - "custom": { - "axisLabel": "Wait Time (\u00b5s)", - "spanNulls": 1800000, - "insertNulls": false, - "showPoints": "auto", - "pointSize": 3 - } - }, - "overrides": [] - } - }, - { - "title": "FullBelowCache Size", - "description": "###### What this is:\n*Number of entries in the FullBelowCache, which tracks subtrees known to be fully present locally.*\n\n###### How it's computed:\n*Current value of the cache size gauge, plotted over time.*\n\n###### Reading it:\n*A stable size is normal; it grows during acquisition and is trimmed by sweeps.*\n\n###### Healthy range:\n*Stable within its configured bound.*\n\n###### Watch for:\n*Unbounded growth suggests the cache is not being swept.*\n\n###### Source:\n[TaggedCache.h](https://github.com/XRPLF/rippled/blob/develop/include/xrpl/basics/TaggedCache.h)\n\n###### Function:\n`TaggedCache::Stats`", - "type": "timeseries", - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 171 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(node_family_full_below_cache_size{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"FullBelowCache Size\", \"\", \"\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "short", - "custom": { - "axisLabel": "Entries", - "spanNulls": 1800000, - "insertNulls": false, - "showPoints": "auto", - "pointSize": 3 - } - }, - "overrides": [] - } - }, - { - "title": "FullBelowCache Hit Rate", - "description": "###### What this is:\n*Hit-rate percentage for the FullBelowCache.*\n\n###### How it's computed:\n*Current value of the cache hit-rate gauge.*\n\n###### Reading it:\n*Higher is better; it shows how often cached subtree knowledge is reused.*\n\n###### Healthy range:\n*Above roughly 50 percent in steady state.*\n\n###### Watch for:\n*A low hit rate during steady state means redundant subtree work and warrants investigation.*\n\n###### Source:\n[TaggedCache.h](https://github.com/XRPLF/rippled/blob/develop/include/xrpl/basics/TaggedCache.h)\n\n###### Function:\n`TaggedCache::Stats`", - "type": "gauge", - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 171 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(node_family_full_below_cache_hit_rate{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Hit Rate\", \"\", \"\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "percent", - "min": 0, - "max": 100, - "thresholds": { - "steps": [ - { - "color": "red", - "value": null - }, - { - "color": "yellow", - "value": 25 - }, - { - "color": "green", - "value": 50 - } - ] - } - }, - "overrides": [] - } - }, - { - "title": "Ledger Publish Gap", - "description": "###### What this is:\n*The lag between published and validated ledger ages, in seconds.*\n\n###### How it's computed:\n*Published ledger age minus validated ledger age, as a single derived value.*\n\n###### Reading it:\n*Near zero is healthy; a positive value is how far publishing trails validation.*\n\n###### Healthy range:\n*Close to zero.*\n\n###### Watch for:\n*A growing gap means the publish pipeline is falling behind and subscribers may see stale data.*\n\n###### Source:\n[LedgerMaster.h](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/LedgerMaster.h)\n\n###### Function:\n`LedgerMaster::Stats::collectMetrics`", - "type": "stat", - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 179 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(ledgermaster_published_ledger_age{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"} - ledgermaster_validated_ledger_age{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Publish Gap\", \"\", \"\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "s", - "thresholds": { - "steps": [ - { - "color": "green", - "value": null + "value": 0 }, { "color": "yellow", @@ -2133,372 +415,590 @@ "value": 10 } ] - } - }, - "overrides": [] - } - }, - { - "title": "State Duration Rate (All States)", - "description": "###### What this is:\n*Which operating mode the node is accumulating time in right now, one line per state, normalized to seconds per second.*\n\n###### How it's computed:\n*Per-second rate of each state's duration counter, scaled from microseconds to seconds. The five lines sum to about 1.0 because the node is always in exactly one state.*\n\n###### Reading it:\n*The line sitting near 1.0 is the state the node is currently in; the others sit at 0. A handover between two lines marks a state change, and its width is how long that state lasted.*\n\n###### Healthy range:\n*Full near 1.0 with every other line at 0.*\n\n###### Watch for:\n*Time accumulating in Connected or Syncing means the node is catching up rather than serving; repeated handovers mean it is flapping.*\n\n###### Keywords:\n- **Operating mode / server state** *(per node)* \u2014 the node's sync level: Disconnected, Connected, Syncing, Tracking, Full.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[NetworkOPs.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/NetworkOPs.cpp)\n\n###### Function:\n`NetworkOPsImp::Stats`\n\n###### References:\n[Server states](https://xrpl.org/docs/concepts/networks-and-servers/rippled-server-states)", - "type": "timeseries", + }, + "unit": "s" + } + }, "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 179 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(rate(state_accounting_full_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) / 1000000, \"series\", \"Full Mode Rate\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(rate(state_accounting_tracking_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) / 1000000, \"series\", \"Tracking Mode Rate\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(rate(state_accounting_syncing_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) / 1000000, \"series\", \"Syncing Mode Rate\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(rate(state_accounting_connected_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) / 1000000, \"series\", \"Connected Mode Rate\", \"\", \"\")" - }, - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(rate(state_accounting_disconnected_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) / 1000000, \"series\", \"Disconnected Mode Rate\", \"\", \"\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "suffix: s/s", - "custom": { - "axisLabel": "Rate (s/s)", - "spanNulls": 1800000, - "insertNulls": false, - "showPoints": "auto", - "pointSize": 3 - } - }, - "overrides": [] - } - }, - { - "title": "All Jobs Execution Time (Detail)", - "description": "###### What this is:\n*Execution time for every non-special job type at the selected quantile.*\n\n###### How it's computed:\n*Per-job-type execution durations aggregated to the chosen quantile per node over a 5-minute window.*\n\n###### Reading it:\n*Lower is better; this is the full picture behind the Key Jobs view.*\n\n###### Healthy range:\n*A few to tens of milliseconds for most jobs.*\n\n###### Watch for:\n*Broad elevation across many job types indicates systemic processing load.*\n\n###### Source:\n[JobTypeData.h](https://github.com/XRPLF/rippled/blob/develop/include/xrpl/core/JobTypeData.h)\n\n###### Function:\n`JobTypeData`", - "type": "timeseries", - "gridPos": { - "h": 8, - "w": 24, - "x": 0, - "y": 187 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(histogram_quantile($quantile, sum by (le, job_type, xrpl_branch, xrpl_node_role) (rate(job_running_us_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"$1\", \"job_type\", \"(.*)\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "\u00b5s", - "custom": { - "axisLabel": "Duration (\u00b5s)", - "spanNulls": 1800000, - "insertNulls": false, - "showPoints": "auto", - "pointSize": 3 - } - }, - "overrides": [] - } - }, - { - "title": "All Jobs Dequeue Wait (Detail)", - "description": "###### What this is:\n*Queue wait time for every non-special job type at the selected quantile.*\n\n###### How it's computed:\n*Per-job-type queue-wait durations aggregated to the chosen quantile per node over a 5-minute window.*\n\n###### Reading it:\n*Lower is better; this is the full picture behind the Key Jobs wait view.*\n\n###### Healthy range:\n*Near zero to a few milliseconds when the queue keeps up.*\n\n###### Watch for:\n*High waits across many job types indicate systemic queue congestion.*\n\n###### Source:\n[JobTypeData.h](https://github.com/XRPLF/rippled/blob/develop/include/xrpl/core/JobTypeData.h)\n\n###### Function:\n`JobTypeData`", - "type": "timeseries", - "gridPos": { - "h": 8, - "w": 24, - "x": 0, - "y": 195 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(histogram_quantile($quantile, sum by (le, job_type, xrpl_branch, xrpl_node_role) (rate(job_queued_us_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"$1\", \"job_type\", \"(.*)\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "\u00b5s", - "custom": { - "axisLabel": "Wait Time (\u00b5s)", - "spanNulls": 1800000, - "insertNulls": false, - "showPoints": "auto", - "pointSize": 3 - } - }, - "overrides": [] - } - }, - { - "title": "Ledger Acquire Duration (Inbound Fetch)", - "description": "###### What this is:\n*Time to fetch a missing ledger from peers, at the 95th percentile.*\n\n###### How it's computed:\n*Per-acquire durations aggregated to their 95th percentile per node over a 5-minute window.*\n\n###### Reading it:\n*Lower is better; populated mainly during sync or back-fill.*\n\n###### Healthy range:\n*Low when synced; higher and more active while catching up.*\n\n###### Watch for:\n*A spike signals the node is falling behind or recovering from a fork.*\n\n###### Source:\n[InboundLedger.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/detail/InboundLedger.cpp)\n\n###### Function:\n`InboundLedger::init`", - "type": "timeseries", - "gridPos": { - "h": 8, + "h": 12, "w": 12, "x": 0, - "y": 203 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(histogram_quantile(0.95, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_duration_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"ledger.acquire\"}[5m]))), \"series\", \"P95 Acquire\", \"\", \"\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "ms", - "custom": { - "axisLabel": "Duration (ms)", - "spanNulls": 1800000, - "insertNulls": false, - "showPoints": "auto", - "pointSize": 3 - } - }, - "overrides": [] - } - }, - { - "title": "Ledger Acquire Rate by Outcome", - "description": "###### What this is:\n*Rate of completed ledger fetches split by outcome (complete or failed).*\n\n###### How it's computed:\n*Per-second rate of finished acquisitions grouped by outcome, per node, over a 5-minute window.*\n\n###### Reading it:\n*Complete should dominate; the failed line should stay near zero.*\n\n###### Healthy range:\n*Complete tracking fetch demand, failed near zero.*\n\n###### Watch for:\n*A rising failed rate means the node cannot fetch needed ledgers from its peers.*\n\n###### Source:\n[InboundLedger.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/detail/InboundLedger.cpp)\n\n###### Function:\n`InboundLedger::init`", - "type": "timeseries", - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 203 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(sum by (outcome, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"ledger.acquire\"}[$__rate_interval])), \"series\", \"$1\", \"outcome\", \"(.*)\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "suffix: acquisitions/s", - "custom": { - "axisLabel": "Acquisitions / Sec", - "spanNulls": 1800000, - "insertNulls": false, - "showPoints": "auto", - "pointSize": 3 - } - }, - "overrides": [] - } - }, - { - "title": "Operating Mode (State Timeline)", - "description": "###### What this is:\n*The node's operating mode over time as a colored timeline (Disconnected, Connected, Syncing, Tracking, Full).*\n\n###### How it's computed:\n*Current value of the server-state gauge mapped to a mode name; equal consecutive samples are merged into a single band.*\n\n###### Reading it:\n*A solid green Full band across the window is the goal; other colors mark periods the node was not fully synced.*\n\n###### Healthy range:\n*Continuously Full (green).*\n\n###### Watch for:\n*Bands of Syncing, Connected, or Disconnected, which pinpoint exactly when the node dropped out of Full.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerServerInfoGauge`", - "type": "state-timeline", - "gridPos": { - "h": 8, - "w": 18, - "x": 0, - "y": 211 - }, - "options": { - "mergeValues": true, - "showValue": "auto", - "alignValue": "center", - "rowHeight": 0.9, - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "expr": "server_info{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"server_state\"}", - "legendFormat": "State [{{service_instance_id}}]" - } - ], - "fieldConfig": { - "defaults": { - "unit": "none", - "mappings": [ - { - "type": "value", - "options": { - "0": { - "text": "Disconnected", - "color": "red" - } - } - }, - { - "type": "value", - "options": { - "1": { - "text": "Connected", - "color": "orange" - } - } - }, - { - "type": "value", - "options": { - "2": { - "text": "Syncing", - "color": "yellow" - } - } - }, - { - "type": "value", - "options": { - "3": { - "text": "Tracking", - "color": "blue" - } - } - }, - { - "type": "value", - "options": { - "4": { - "text": "Full", - "color": "green" - } - } - } - ], - "custom": { - "fillOpacity": 80, - "lineWidth": 0, - "spanNulls": 1800000, - "insertNulls": false - } - }, - "overrides": [] - } - }, - { - "title": "% Time in Full", - "description": "###### What this is:\n*The share of recent wall-clock time the node spent in Full mode.*\n\n###### How it's computed:\n*Per-second rate of the Full-mode duration counter divided by the sum of the per-second rates of all five mode duration counters.*\n\n###### Reading it:\n*Higher is better; 1.0 means the node was fully synced for the entire window.*\n\n###### Healthy range:\n*At or above 0.99.*\n\n###### Watch for:\n*Values dropping below 0.9, meaning the node spent meaningful time outside Full mode.*\n\n###### Source:\n[NetworkOPs.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/NetworkOPs.cpp)\n\n###### Function:\n`NetworkOPsImp::Stats`", - "type": "stat", - "gridPos": { - "h": 8, - "w": 6, - "x": 18, - "y": 211 + "y": 28 }, + "id": 71, "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, - "orientation": "auto", + "showPercentChange": false, "textMode": "auto", - "colorMode": "value", "tooltip": { "maxHeight": 600, - "mode": "single", - "sort": "none" - } + "mode": "multi", + "sort": "desc" + }, + "wideLayout": true }, + "pluginVersion": "13.2.0-28926505616", "targets": [ { "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(state_accounting_full_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) / (rate(state_accounting_disconnected_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_connected_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_syncing_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_tracking_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_full_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])), \"series\", \"% Time in Full\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(ledgermaster_published_ledger_age{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"} - ledgermaster_validated_ledger_age{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Publish Gap\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" } ], + "title": "Ledger Publish Gap", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*Latency of the I/O service loop, at the 95th percentile in milliseconds.*\n\n###### How it's computed:\n*I/O loop latency samples aggregated to their 95th percentile per node over a 5-minute window.*\n\n###### Reading it:\n*Lower is better; it reflects how promptly queued I/O work runs.*\n\n###### Healthy range:\n*A few milliseconds; brief spikes are tolerable.*\n\n###### Watch for:\n*Sustained values above a few hundred ms indicate thread-pool saturation or blocking operations.*\n\n###### Keywords:\n- **I/O scheduler** *(per node)* \u2014 the queue that serializes NodeStore disk reads and writes.\n- **NodeStore** *(per node)* \u2014 the key-value object store holding ledger data (tree nodes), backed by NuDB.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[Application.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/main/Application.cpp)\n\n###### Function:\n`ApplicationImp`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#nodestore)", "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "percentunit", - "min": 0, + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Latency (ms)", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 3, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": 1800000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ms" + } + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 12, + "y": 28 + }, + "id": 72, + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(histogram_quantile(0.95, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(ios_latency_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"P95 I/O Latency\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" + } + ], + "title": "I/O Latency", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 40 + }, + "id": 121, + "title": "Operating Mode", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*Fraction of recent wall-clock time the node spent in each operating mode (Disconnected, Connected, Syncing, Tracking, Full).*\n\n###### How it's computed:\n*Per-second rate of each per-mode duration counter divided by the sum of all five mode rates, giving each mode's time share.*\n\n###### Reading it:\n*The Full share should sit at or near 1.0 and dominate; other shares should be near 0.*\n\n###### Healthy range:\n*Full share close to 1.0.*\n\n###### Watch for:\n*Share accumulating in Syncing, Connected, or Disconnected means the node is not staying fully synced.*\n\n###### Keywords:\n- **Operating mode / server state** *(per node)* \u2014 the node's sync level: Disconnected, Connected, Syncing, Tracking, Full (and Validating/Proposing).\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[NetworkOPs.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/NetworkOPs.cpp)\n\n###### Function:\n`NetworkOPsImp::Stats`\n\n###### References:\n[Operating mode / server state](https://xrpl.org/docs/references/http-websocket-apis/api-conventions/xrpld-server-states) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#operating-mode-server-state)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Time Share", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 3, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": 1800000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "max": 1, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + } + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 0, + "y": 41 + }, + "id": 74, + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(rate(state_accounting_full_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) / (rate(state_accounting_disconnected_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_connected_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_syncing_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_tracking_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_full_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])), \"series\", \"Full\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(rate(state_accounting_tracking_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) / (rate(state_accounting_disconnected_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_connected_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_syncing_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_tracking_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_full_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])), \"series\", \"Tracking\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(rate(state_accounting_syncing_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) / (rate(state_accounting_disconnected_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_connected_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_syncing_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_tracking_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_full_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])), \"series\", \"Syncing\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(rate(state_accounting_connected_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) / (rate(state_accounting_disconnected_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_connected_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_syncing_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_tracking_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_full_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])), \"series\", \"Connected\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "D" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(rate(state_accounting_disconnected_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) / (rate(state_accounting_disconnected_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_connected_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_syncing_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_tracking_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_full_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])), \"series\", \"Disconnected\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "E" + } + ], + "title": "Operating Mode (Time Share)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*Cumulative count of transitions into each operating mode.*\n\n###### How it's computed:\n*Current value of each per-mode transition counter, plotted as lines.*\n\n###### Reading it:\n*Flat lines are healthy; steps up mean the node changed mode.*\n\n###### Healthy range:\n*Few transitions once the node is stable in Full mode.*\n\n###### Watch for:\n*Frequent transitions out of Full, or into Disconnected or Syncing, indicate instability.*\n\n###### Keywords:\n- **Operating mode / server state** *(per node)* \u2014 the node's sync level: Disconnected, Connected, Syncing, Tracking, Full (and Validating/Proposing).\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[NetworkOPs.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/NetworkOPs.cpp)\n\n###### Function:\n`NetworkOPsImp::Stats`\n\n###### References:\n[Operating mode / server state](https://xrpl.org/docs/references/http-websocket-apis/api-conventions/xrpld-server-states) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#operating-mode-server-state)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Transitions", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 3, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": 1800000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + } + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 12, + "y": 41 + }, + "id": 75, + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(state_accounting_full_transitions{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Full\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(state_accounting_tracking_transitions{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Tracking\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(state_accounting_syncing_transitions{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Syncing\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(state_accounting_connected_transitions{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Connected\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "D" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(state_accounting_disconnected_transitions{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Disconnected\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "E" + } + ], + "title": "Operating Mode Transitions", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*Which operating mode the node is accumulating time in right now, one line per state, normalized to seconds per second.*\n\n###### How it's computed:\n*Per-second rate of each state's duration counter, scaled from microseconds to seconds. The five lines sum to about 1.0 because the node is always in exactly one state.*\n\n###### Reading it:\n*The line sitting near 1.0 is the state the node is currently in; the others sit at 0. A handover between two lines marks a state change, and its width is how long that state lasted.*\n\n###### Healthy range:\n*Full near 1.0 with every other line at 0.*\n\n###### Watch for:\n*Time accumulating in Connected or Syncing means the node is catching up rather than serving; repeated handovers mean it is flapping.*\n\n###### Keywords:\n- **Operating mode / server state** *(per node)* \u2014 the node's sync level: Disconnected, Connected, Syncing, Tracking, Full.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[NetworkOPs.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/NetworkOPs.cpp)\n\n###### Function:\n`NetworkOPsImp::Stats`\n\n###### References:\n[Server states](https://xrpl.org/docs/concepts/networks-and-servers/rippled-server-states)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Rate (s/s)", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 3, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": 1800000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "suffix: s/s" + } + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 0, + "y": 53 + }, + "id": 76, + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(rate(state_accounting_full_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) / 1000000, \"series\", \"Full Mode Rate\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(rate(state_accounting_tracking_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) / 1000000, \"series\", \"Tracking Mode Rate\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(rate(state_accounting_syncing_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) / 1000000, \"series\", \"Syncing Mode Rate\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(rate(state_accounting_connected_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) / 1000000, \"series\", \"Connected Mode Rate\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "D" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(rate(state_accounting_disconnected_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) / 1000000, \"series\", \"Disconnected Mode Rate\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "E" + } + ], + "title": "State Duration Rate (All States)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*The share of recent wall-clock time the node spent in Full mode.*\n\n###### How it's computed:\n*Per-second rate of the Full-mode duration counter divided by the sum of the per-second rates of all five mode duration counters.*\n\n###### Reading it:\n*Higher is better; 1.0 means the node was fully synced for the entire window.*\n\n###### Healthy range:\n*At or above 0.99.*\n\n###### Watch for:\n*Values dropping below 0.9, meaning the node spent meaningful time outside Full mode.*\n\n###### Keywords:\n- **Operating mode / server state** *(per node)* \u2014 the node's sync level: Disconnected, Connected, Syncing, Tracking, Full (and Validating/Proposing).\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[NetworkOPs.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/NetworkOPs.cpp)\n\n###### Function:\n`NetworkOPsImp::Stats`\n\n###### References:\n[Operating mode / server state](https://xrpl.org/docs/references/http-websocket-apis/api-conventions/xrpld-server-states) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#operating-mode-server-state)", + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "max": 1, + "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "red", - "value": null + "value": 0 }, { "color": "yellow", @@ -2510,10 +1010,4134 @@ } ] }, - "custom": {} + "unit": "percentunit" + } + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 12, + "y": 53 + }, + "id": 77, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false }, - "overrides": [] - } + "showPercentChange": false, + "textMode": "auto", + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + }, + "wideLayout": true + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(rate(state_accounting_full_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) / (rate(state_accounting_disconnected_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_connected_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_syncing_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_tracking_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) + rate(state_accounting_full_duration{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])), \"series\", \"% Time in Full\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" + } + ], + "title": "% Time in Full", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*The node's operating mode over time as a colored timeline (Disconnected, Connected, Syncing, Tracking, Full).*\n\n###### How it's computed:\n*Current value of the server-state gauge mapped to a mode name; equal consecutive samples are merged into a single band.*\n\n###### Reading it:\n*A solid green Full band across the window is the goal; other colors mark periods the node was not fully synced.*\n\n###### Healthy range:\n*Continuously Full (green).*\n\n###### Watch for:\n*Bands of Syncing, Connected, or Disconnected, which pinpoint exactly when the node dropped out of Full.*\n\n###### Keywords:\n- **Operating mode / server state** *(per node)* \u2014 the node's sync level: Disconnected, Connected, Syncing, Tracking, Full (and Validating/Proposing).\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerServerInfoGauge`\n\n###### References:\n[Operating mode / server state](https://xrpl.org/docs/references/http-websocket-apis/api-conventions/xrpld-server-states) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#operating-mode-server-state)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "custom": { + "axisPlacement": "auto", + "fillOpacity": 80, + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineWidth": 0, + "spanNulls": 1800000 + }, + "mappings": [ + { + "options": { + "0": { + "color": "red", + "text": "Disconnected" + } + }, + "type": "value" + }, + { + "options": { + "1": { + "color": "orange", + "text": "Connected" + } + }, + "type": "value" + }, + { + "options": { + "2": { + "color": "yellow", + "text": "Syncing" + } + }, + "type": "value" + }, + { + "options": { + "3": { + "color": "blue", + "text": "Tracking" + } + }, + "type": "value" + }, + { + "options": { + "4": { + "color": "green", + "text": "Full" + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + } + }, + "gridPos": { + "h": 24, + "w": 24, + "x": 0, + "y": 65 + }, + "id": 78, + "options": { + "alignValue": "left", + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "displayMode": "list", + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "mergeValues": true, + "rowHeight": 0.9, + "showValue": "auto", + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc", + "maxHeight": 600 + } + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "server_info{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"server_state\"}", + "legendFormat": "State [{{service_instance_id}}]", + "refId": "A" + } + ], + "title": "Operating Mode (State Timeline)", + "type": "state-timeline" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 89 + }, + "id": 122, + "title": "NodeStore I/O", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*Object-store read, found and write operation rates. The found series is `node_reads_hit`, which counts fetches that returned an object whatever served them, so it is not a cache-hit count.*\n\n###### How it's computed:\n*Per-second rates of the read, found and write counters, plotted as lines.*\n\n###### Reading it:\n*Steadily rising lines are normal; the slope reflects store activity.*\n\n###### Healthy range:\n*Reads and writes rising smoothly, with found tracking almost all reads on a node that has the data.*\n\n###### Watch for:\n*A sudden surge in reads or writes signals heavy back-end I/O, from sync, replay, or query load.*\n\n###### Keywords:\n- **NodeStore** *(per node)* \u2014 the key-value object store holding ledger data (tree nodes), backed by NuDB.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerNodeStoreGauge`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#nodestore)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Operations / Sec", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": 1800000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "suffix: operations/s" + } + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 0, + "y": 90 + }, + "id": 80, + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(rate(nodestore_state{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"node_reads_total\"}[$__rate_interval]), \"series\", \"Reads Total\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(rate(nodestore_state{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"node_reads_hit\"}[$__rate_interval]), \"series\", \"Reads Found\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(rate(nodestore_state{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"node_writes\"}[$__rate_interval]), \"series\", \"Writes Total\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "C" + } + ], + "title": "NodeStore Throughput", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*Object-store read, found and write operation rates. The found series is `node_reads_hit`, which counts fetches that returned an object whatever served them, so it is not a cache-hit count.*\n\n###### How it's computed:\n*Per-second rates of the read, found and write counters, plotted as lines.*\n\n###### Reading it:\n*Steadily rising lines are normal; the slope reflects store activity.*\n\n###### Healthy range:\n*Reads and writes rising smoothly, with found tracking almost all reads on a node that has the data.*\n\n###### Watch for:\n*A sudden surge in reads or writes signals heavy back-end I/O, from sync, replay, or query load.*\n\n###### Keywords:\n- **NodeStore** *(per node)* \u2014 the key-value object store holding ledger data (tree nodes), backed by NuDB.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerNodeStoreGauge`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#nodestore)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Found Ratio", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": 1800000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + } + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 12, + "y": 90 + }, + "id": 81, + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(rate(nodestore_state{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"node_reads_hit\"}[$__rate_interval]) / ignoring(metric) rate(nodestore_state{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"node_reads_total\"}[$__rate_interval]), \"series\", \"Found Ratio\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" + } + ], + "title": "NodeStore Read Found Ratio", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*Instantaneous write-load score and read-queue depth of the object store.*\n\n###### How it's computed:\n*Current values of the write-load and read-queue gauges, plotted over time.*\n\n###### Reading it:\n*Lower is better for both; short, flat lines are healthy.*\n\n###### Healthy range:\n*Write load near zero and read queue in low double digits or less.*\n\n###### Watch for:\n*High write load means back-end pressure; a high read queue means the prefetch threads are saturated.*\n\n###### Keywords:\n- **Transaction queue (TxQ)** *(per node)* \u2014 holds transactions that meet local cost but not the open-ledger cost, to include in a later ledger.\n- **NodeStore** *(per node)* \u2014 the key-value object store holding ledger data (tree nodes), backed by NuDB.\n- **Read threads / read queue / write load** *(per node)* \u2014 NodeStore back-end I/O internals \u2014 worker threads reading, their queue depth, and write pressure.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerNodeStoreGauge`\n\n###### References:\n[Transaction queue (TxQ)](https://xrpl.org/docs/concepts/transactions/transaction-queue) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#transaction-queue-txq)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Count", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": 1800000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "yellow", + "value": 100 + }, + { + "color": "red", + "value": 1000 + } + ] + }, + "unit": "short" + } + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 0, + "y": 102 + }, + "id": 82, + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(nodestore_state{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"write_load\"}, \"series\", \"Write Load\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(nodestore_state{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"read_queue\"}, \"series\", \"Read Queue\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "B" + } + ], + "title": "NodeStore Write Load & Read Queue", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*Cumulative bytes read from and written to the object-store back end.*\n\n###### How it's computed:\n*Current values of the bytes-read and bytes-written counters, plotted as lines.*\n\n###### Reading it:\n*Steadily rising lines are normal; the slope shows throughput.*\n\n###### Healthy range:\n*Smooth growth consistent with ledger and query activity.*\n\n###### Watch for:\n*A sharp acceleration indicates a heavy I/O phase such as sync, replay, or large queries.*\n\n###### Keywords:\n- **NodeStore** *(per node)* \u2014 the key-value object store holding ledger data (tree nodes), backed by NuDB.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerNodeStoreGauge`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#nodestore)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Throughput", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": 1800000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "Bps" + } + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 12, + "y": 102 + }, + "id": 83, + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(rate(nodestore_state{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"node_read_bytes\"}[$__rate_interval]), \"series\", \"Bytes Read\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(rate(nodestore_state{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"node_written_bytes\"}[$__rate_interval]), \"series\", \"Bytes Written\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "B" + } + ], + "title": "NodeStore Bytes Read/Written", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*Read-thread utilization, bundled read count, and cumulative read time for the object store.*\n\n###### How it's computed:\n*Current values of the running/total read-thread gauges, read-bundle gauge, and cumulative read-duration counter, plotted as lines.*\n\n###### Reading it:\n*Running threads well below total means spare capacity; a rising duration line reflects time spent in read I/O.*\n\n###### Healthy range:\n*Running threads below the total count most of the time.*\n\n###### Watch for:\n*Running threads pinned at the total for long periods means read I/O is saturated.*\n\n###### Keywords:\n- **Read threads / read queue / write load** *(per node)* \u2014 NodeStore back-end I/O internals \u2014 worker threads reading, their queue depth, and write pressure.\n- **NodeStore** *(per node)* \u2014 the key-value object store holding ledger data (tree nodes), backed by NuDB.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerNodeStoreGauge`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#read-threads-read-queue-write-load)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Threads / Bundle", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": 1800000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + } + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 0, + "y": 114 + }, + "id": 84, + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(nodestore_state{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"read_threads_running\"}, \"series\", \"Read Threads Running\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(nodestore_state{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"read_threads_total\"}, \"series\", \"Read Threads Total\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(nodestore_state{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"read_request_bundle\"}, \"series\", \"Read Request Bundle\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "C" + } + ], + "title": "NodeStore Read Threads", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*Read-thread utilization, bundled read count, and cumulative read time for the object store.*\n\n###### How it's computed:\n*Current values of the running/total read-thread gauges, read-bundle gauge, and cumulative read-duration counter, plotted as lines.*\n\n###### Reading it:\n*Running threads well below total means spare capacity; a rising duration line reflects time spent in read I/O.*\n\n###### Healthy range:\n*Running threads below the total count most of the time.*\n\n###### Watch for:\n*Running threads pinned at the total for long periods means read I/O is saturated.*\n\n###### Keywords:\n- **Read threads / read queue / write load** *(per node)* \u2014 NodeStore back-end I/O internals \u2014 worker threads reading, their queue depth, and write pressure.\n- **NodeStore** *(per node)* \u2014 the key-value object store holding ledger data (tree nodes), backed by NuDB.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerNodeStoreGauge`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#read-threads-read-queue-write-load)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Threads / Bundle", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": 1800000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + } + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 12, + "y": 114 + }, + "id": 85, + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(rate(nodestore_state{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"node_reads_duration_us\"}[$__rate_interval]) / 1e6, \"series\", \"Read Busy Ratio\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" + } + ], + "title": "NodeStore Read Busy Ratio", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 126 + }, + "id": 123, + "title": "Jobs", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*Number of jobs currently waiting in the internal work queue.*\n\n###### How it's computed:\n*Current value of the job-count gauge, plotted over time.*\n\n###### Reading it:\n*Lower is better; a near-flat low line means the node keeps up with its workload.*\n\n###### Healthy range:\n*Low single digits at idle, brief bumps under load.*\n\n###### Watch for:\n*A sustained high depth means the node cannot process work fast enough, common during replay, heavy RPC, or a request flood.*\n\n###### Keywords:\n- **Job queue / job type** *(per node)* \u2014 xrpld's worker-thread pool; every unit of background work is enqueued under a named job type.\n- **Deferred job** *(per node)* \u2014 a job held back because its type is already at its concurrency limit; the leading indicator of queue backpressure.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[JobQueue.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/core/detail/JobQueue.cpp)\n\n###### Function:\n`JobQueue`\n\n###### References:\n[Job queue / job type](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#job-queue-job-type) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Jobs", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 3, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": 1800000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + } + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 0, + "y": 127 + }, + "id": 87, + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(jobq_job_count{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Job Queue Depth\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" + } + ], + "title": "Job Queue Depth", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*Execution time for the most critical job types at the selected quantile.*\n\n###### How it's computed:\n*Per-job-type execution durations aggregated to the chosen quantile per node over a 5-minute window.*\n\n###### Reading it:\n*Lower is better; each line is one job type's run time.*\n\n###### Healthy range:\n*A few to tens of milliseconds for most jobs; workload-dependent.*\n\n###### Watch for:\n*Spikes on key jobs (accept, transaction, write) indicate processing bottlenecks.*\n\n###### Keywords:\n- **Job queue / job type** *(per node)* \u2014 xrpld's worker-thread pool; every unit of background work is enqueued under a named job type.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[JobTypeData.h](https://github.com/XRPLF/rippled/blob/develop/include/xrpl/core/JobTypeData.h)\n\n###### Function:\n`JobTypeData`\n\n###### References:\n[Job queue / job type](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#job-queue-job-type) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Duration (\u00b5s)", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 3, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": 1800000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "\u00b5s" + } + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 12, + "y": 127 + }, + "id": 88, + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_running_us_bucket{job_type=\"acceptLedger\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Accept Ledger\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_running_us_bucket{job_type=\"advanceLedger\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Advance Ledger\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_running_us_bucket{job_type=\"transaction\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Transaction\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_running_us_bucket{job_type=\"writeObjects\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Write Objects\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "D" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_running_us_bucket{job_type=\"heartbeat\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Heartbeat\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "E" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_running_us_bucket{job_type=\"sweep\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Sweep\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "F" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_running_us_bucket{job_type=\"trustedValidation\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Trusted Validation\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "G" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_running_us_bucket{job_type=\"trustedProposal\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Trusted Proposal\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "H" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_running_us_bucket{job_type=\"publishNewLedger\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Publish New Ledger\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "I" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_running_us_bucket{job_type=\"clientRPC\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Client RPC\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "J" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_running_us_bucket{job_type=\"ledgerData\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Ledger Data\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "K" + } + ], + "title": "Key Jobs Execution Time (q$quantile)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*Time critical jobs wait in the queue before running, at the selected quantile.*\n\n###### How it's computed:\n*Per-job-type queue-wait durations aggregated to the chosen quantile per node over a 5-minute window.*\n\n###### Reading it:\n*Lower is better; each line is one job type's wait time.*\n\n###### Healthy range:\n*Near zero to a few milliseconds when the queue is keeping up.*\n\n###### Watch for:\n*High waits mean the queue is backlogged and jobs are scheduled late.*\n\n###### Keywords:\n- **Job queue / job type** *(per node)* \u2014 xrpld's worker-thread pool; every unit of background work is enqueued under a named job type.\n- **Deferred job** *(per node)* \u2014 a job held back because its type is already at its concurrency limit; the leading indicator of queue backpressure.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[JobTypeData.h](https://github.com/XRPLF/rippled/blob/develop/include/xrpl/core/JobTypeData.h)\n\n###### Function:\n`JobTypeData`\n\n###### References:\n[Deferred job](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#deferred-job) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Wait Time (\u00b5s)", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 3, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": 1800000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "\u00b5s" + } + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 0, + "y": 139 + }, + "id": 89, + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_queued_us_bucket{job_type=\"acceptLedger\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Accept Ledger\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_queued_us_bucket{job_type=\"advanceLedger\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Advance Ledger\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_queued_us_bucket{job_type=\"transaction\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Transaction\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_queued_us_bucket{job_type=\"writeObjects\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Write Objects\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "D" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_queued_us_bucket{job_type=\"heartbeat\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Heartbeat\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "E" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_queued_us_bucket{job_type=\"sweep\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Sweep\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "F" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_queued_us_bucket{job_type=\"trustedValidation\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Trusted Validation\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "G" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_queued_us_bucket{job_type=\"trustedProposal\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Trusted Proposal\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "H" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_queued_us_bucket{job_type=\"publishNewLedger\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Publish New Ledger\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "I" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_queued_us_bucket{job_type=\"clientRPC\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Client RPC\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "J" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(histogram_quantile($quantile, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_queued_us_bucket{job_type=\"ledgerData\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"Ledger Data\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "K" + } + ], + "title": "Key Jobs Dequeue Wait Time (q$quantile)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*Execution time for every non-special job type at the selected quantile.*\n\n###### How it's computed:\n*Per-job-type execution durations aggregated to the chosen quantile per node over a 5-minute window.*\n\n###### Reading it:\n*Lower is better; this is the full picture behind the Key Jobs view.*\n\n###### Healthy range:\n*A few to tens of milliseconds for most jobs.*\n\n###### Watch for:\n*Broad elevation across many job types indicates systemic processing load.*\n\n###### Keywords:\n- **Job queue / job type** *(per node)* \u2014 xrpld's worker-thread pool; every unit of background work is enqueued under a named job type.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[JobTypeData.h](https://github.com/XRPLF/rippled/blob/develop/include/xrpl/core/JobTypeData.h)\n\n###### Function:\n`JobTypeData`\n\n###### References:\n[Job queue / job type](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#job-queue-job-type) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Duration (\u00b5s)", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 3, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": 1800000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "\u00b5s" + } + }, + "gridPos": { + "h": 16, + "w": 24, + "x": 0, + "y": 151 + }, + "id": 90, + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(histogram_quantile($quantile, sum by (service_instance_id, le, job_type, xrpl_branch, xrpl_node_role) (rate(job_running_us_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"$1\", \"job_type\", \"(.*)\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" + } + ], + "title": "All Jobs Execution Time (Detail)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*Queue wait time for every non-special job type at the selected quantile.*\n\n###### How it's computed:\n*Per-job-type queue-wait durations aggregated to the chosen quantile per node over a 5-minute window.*\n\n###### Reading it:\n*Lower is better; this is the full picture behind the Key Jobs wait view.*\n\n###### Healthy range:\n*Near zero to a few milliseconds when the queue keeps up.*\n\n###### Watch for:\n*High waits across many job types indicate systemic queue congestion.*\n\n###### Keywords:\n- **Job queue / job type** *(per node)* \u2014 xrpld's worker-thread pool; every unit of background work is enqueued under a named job type.\n- **Deferred job** *(per node)* \u2014 a job held back because its type is already at its concurrency limit; the leading indicator of queue backpressure.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[JobTypeData.h](https://github.com/XRPLF/rippled/blob/develop/include/xrpl/core/JobTypeData.h)\n\n###### Function:\n`JobTypeData`\n\n###### References:\n[Deferred job](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#deferred-job) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Wait Time (\u00b5s)", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 3, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": 1800000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "\u00b5s" + } + }, + "gridPos": { + "h": 16, + "w": 24, + "x": 0, + "y": 167 + }, + "id": 91, + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(histogram_quantile($quantile, sum by (service_instance_id, le, job_type, xrpl_branch, xrpl_node_role) (rate(job_queued_us_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"$1\", \"job_type\", \"(.*)\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" + } + ], + "title": "All Jobs Dequeue Wait (Detail)", + "type": "timeseries" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 183 + }, + "id": 124, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*Hit rates for the SLE, Ledger, and AcceptedLedger caches, from 0 to 1.*\n\n###### How it's computed:\n*Current values of the per-cache hit-rate gauges, plotted as lines.*\n\n###### Reading it:\n*Higher is better; each line is the fraction of lookups served from cache.*\n\n###### Healthy range:\n*Above roughly 0.8 in steady state.*\n\n###### Watch for:\n*Low or falling hit rates indicate cache thrashing and extra back-end reads.*\n\n###### Keywords:\n- **Caches (SLE / Ledger / TreeNode / FullBelow / AcceptedLedger)** *(per node)* \u2014 in-memory caches that avoid re-reading or re-computing ledger data; higher hit rates mean less back-end work.\n- **NodeStore** *(per node)* \u2014 the key-value object store holding ledger data (tree nodes), backed by NuDB.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerCacheHitRateGauge`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#caches)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Hit Rate", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 3, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": 1800000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "max": 1, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + } + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 0, + "y": 180 + }, + "id": 93, + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(cache_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"SLE_hit_rate\"}, \"series\", \"SLE Hit Rate\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(cache_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"ledger_hit_rate\"}, \"series\", \"Ledger Hit Rate\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(cache_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"AL_hit_rate\"}, \"series\", \"AcceptedLedger Hit Rate\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "C" + } + ], + "title": "Cache Hit Rates", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*Entry counts for the TreeNode cache and track set, the FullBelow cache, and the AcceptedLedger cache.*\n\n###### How it's computed:\n*Current values of the per-cache size gauges, plotted as lines.*\n\n###### Reading it:\n*Stable lines are normal; sizes grow with working set and shrink after sweeps.*\n\n###### Healthy range:\n*Stable within configured limits.*\n\n###### Watch for:\n*Unbounded growth suggests memory pressure or a cache not being swept.*\n\n###### Keywords:\n- **Caches (SLE / Ledger / TreeNode / FullBelow / AcceptedLedger)** *(per node)* \u2014 in-memory caches that avoid re-reading or re-computing ledger data; higher hit rates mean less back-end work.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerCacheHitRateGauge`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#caches)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Entries", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": 1800000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + } + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 12, + "y": 180 + }, + "id": 94, + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(cache_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"treenode_cache_size\"}, \"series\", \"TreeNode Cache\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(cache_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"treenode_track_size\"}, \"series\", \"TreeNode Track\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(cache_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"fullbelow_size\"}, \"series\", \"FullBelow\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(cache_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"AL_size\"}, \"series\", \"AcceptedLedger Size\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "D" + } + ], + "title": "Cache Sizes", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*Number of entries in the FullBelowCache, which tracks subtrees known to be fully present locally.*\n\n###### How it's computed:\n*Current value of the cache size gauge, plotted over time.*\n\n###### Reading it:\n*A stable size is normal; it grows during acquisition and is trimmed by sweeps.*\n\n###### Healthy range:\n*Stable within its configured bound.*\n\n###### Watch for:\n*Unbounded growth suggests the cache is not being swept.*\n\n###### Keywords:\n- **Caches (SLE / Ledger / TreeNode / FullBelow / AcceptedLedger)** *(per node)* \u2014 in-memory caches that avoid re-reading or re-computing ledger data; higher hit rates mean less back-end work.\n- **Ledger acquire (inbound fetch)** *(per node)* \u2014 fetching a specific missing ledger from peers; tracked by duration and outcome (complete/failed).\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[TaggedCache.h](https://github.com/XRPLF/rippled/blob/develop/include/xrpl/basics/TaggedCache.h)\n\n###### Function:\n`TaggedCache::Stats`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#caches)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Entries", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 3, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": 1800000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + } + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 0, + "y": 192 + }, + "id": 95, + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(node_family_full_below_cache_size{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"FullBelowCache Size\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" + } + ], + "title": "FullBelowCache Size", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*Hit-rate percentage for the FullBelowCache.*\n\n###### How it's computed:\n*Current value of the cache hit-rate gauge.*\n\n###### Reading it:\n*Higher is better; it shows how often cached subtree knowledge is reused.*\n\n###### Healthy range:\n*Above roughly 50 percent in steady state.*\n\n###### Watch for:\n*A low hit rate during steady state means redundant subtree work and warrants investigation.*\n\n###### Keywords:\n- **Caches (SLE / Ledger / TreeNode / FullBelow / AcceptedLedger)** *(per node)* \u2014 in-memory caches that avoid re-reading or re-computing ledger data; higher hit rates mean less back-end work.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[TaggedCache.h](https://github.com/XRPLF/rippled/blob/develop/include/xrpl/basics/TaggedCache.h)\n\n###### Function:\n`TaggedCache::Stats`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#caches)", + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": 0 + }, + { + "color": "yellow", + "value": 25 + }, + { + "color": "green", + "value": 50 + } + ] + }, + "unit": "percent" + } + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 12, + "y": 192 + }, + "id": 96, + "options": { + "barShape": "flat", + "barWidthFactor": 0.5, + "effects": { + "barGlow": false, + "centerGlow": false, + "gradient": true + }, + "endpointMarker": "point", + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "segmentCount": 1, + "segmentSpacing": 0.3, + "shape": "gauge", + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto", + "sparkline": true, + "textMode": "auto", + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(node_family_full_below_cache_hit_rate{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Hit Rate\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" + } + ], + "title": "FullBelowCache Hit Rate", + "type": "gauge" + } + ], + "title": "Caches", + "type": "row" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 184 + }, + "id": 125, + "title": "Server Info", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*Which sync state each node is in right now: Disconnected, Connected, Syncing, Tracking or Full. Only a Full node holds the current validated ledger and can answer authoritatively.*\n\n###### How it's computed:\n*The `server_state` gauge, the node's operating mode as an integer 0-4, value-mapped to a name and colour: 0 Disconnected (red), 1 Connected (yellow), 2 Syncing (orange), 3 Tracking (blue), 4 Full (green). No rate or aggregation \u2014 it is the instantaneous state.*\n\n###### Reading it:\n*One tile per node. Green FULL is the steady state; any other colour says the node is not yet serving the current ledger and how far along it is.*\n\n###### Healthy range:\n*FULL on every node.*\n\n###### Watch for:\n*A node leaving FULL and staying out, or flapping between TRACKING and FULL \u2014 that points at ledger acquisition falling behind rather than a connectivity fault. Cross-check Operating Mode (Time Share) and Validated Ledger Age.*\n\n###### Keywords:\n- **Operating mode / server state** *(per node)* \u2014 the node's sync level: Disconnected, Connected, Syncing, Tracking, Full (and Validating/Proposing).\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerServerInfoGauge`\n\n###### References:\n[Operating mode / server state](https://xrpl.org/docs/references/http-websocket-apis/api-conventions/xrpld-server-states) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#operating-mode-server-state)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "0": { + "color": "dark-red", + "index": 0, + "text": "DISCONNECTED" + }, + "1": { + "color": "yellow", + "index": 1, + "text": "CONNECTED" + }, + "2": { + "color": "orange", + "index": 2, + "text": "SYNCING" + }, + "3": { + "color": "light-blue", + "index": 3, + "text": "TRACKING" + }, + "4": { + "color": "green", + "index": 4, + "text": "FULL" + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "DISCONNECTED" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "CONNECTED" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "SYNCING" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "TRACKING" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "FULL" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 0, + "y": 185 + }, + "id": 98, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value_and_name", + "wideLayout": true, + "tooltip": {} + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "app": "grafana-assistant-app", + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "server_info{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"server_state\"}", + "instant": false, + "legendFormat": "{{service_instance_id}} [{{xrpl_branch}}]", + "queryType": "range", + "range": true, + "refId": "A" + } + ], + "title": "Server State", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*How long the server process has been running, in seconds.*\n\n###### How it's computed:\n*Current value of the uptime gauge.*\n\n###### Reading it:\n*Higher is generally better; a reset to a small value means the process restarted.*\n\n###### Healthy range:\n*Continuously increasing.*\n\n###### Watch for:\n*An unexpected drop to near zero indicates a restart or crash.*\n\n###### Keywords:\n- **Uptime** *(per node)* \u2014 seconds since the server process started.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerServerInfoGauge`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-RdYlGr" + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "dthms" + } + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 12, + "y": 185 + }, + "id": 99, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value_and_name", + "wideLayout": true, + "tooltip": {} + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "server_info{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"uptime\"}", + "instant": true, + "legendFormat": "{{service_instance_id}}", + "refId": "A" + } + ], + "title": "Uptime", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*Total connected peers, inbound plus outbound.*\n\n###### How it's computed:\n*Current value of the peer-count gauge.*\n\n###### Reading it:\n*A stable count in the healthy range is good; too few limits connectivity.*\n\n###### Healthy range:\n*Workload- and config-dependent, typically 10 or more.*\n\n###### Watch for:\n*A sudden drop points to network or connectivity problems; an unusually high inbound count can indicate connection-flood pressure.*\n\n###### Keywords:\n- **Peer** *(per node)* \u2014 another server this node holds a protocol connection to.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerServerInfoGauge`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": 0 + }, + { + "color": "yellow", + "value": 5 + }, + { + "color": "green", + "value": 10 + } + ] + }, + "unit": "short" + } + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 0, + "y": 197 + }, + "id": 100, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value_and_name", + "wideLayout": true, + "tooltip": {} + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "server_info{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"peers\"}", + "instant": true, + "legendFormat": "{{service_instance_id}}", + "refId": "A" + } + ], + "title": "Peer Count", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*Sequence number of the current open ledger.*\n\n###### How it's computed:\n*Current value of the open-ledger index gauge.*\n\n###### Reading it:\n*Should climb steadily; the gap above the validated sequence is the ledgers in flight.*\n\n###### Healthy range:\n*One or two ahead of the validated sequence.*\n\n###### Watch for:\n*A large or growing gap above the validated sequence means validation is lagging behind ledger creation.*\n\n###### Keywords:\n- **Ledger index** *(network-wide)* \u2014 the sequence number identifying a ledger version; increases by one each close.\n- **Open ledger** *(per node)* \u2014 the temporary workspace ledger where incoming transactions are provisionally applied before a close.\n- **Validated ledger** *(network-wide)* \u2014 a ledger confirmed final by the trusted validator quorum; its contents never change.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerServerInfoGauge`\n\n###### References:\n[Ledger index](https://xrpl.org/docs/references/protocol/data-types/basic-data-types#ledger-index) \u00b7 [Open ledger](https://xrpl.org/docs/concepts/ledgers/open-closed-validated-ledgers) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#ledger-index)", + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "none" + } + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 12, + "y": 197 + }, + "id": 103, + "options": { + "colorMode": "none", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + }, + "wideLayout": true + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(server_info{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"ledger_current_index\"}, \"series\", \"Current Ledger\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" + } + ], + "title": "Current Ledger Index", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*The spread in validated ledger sequence across all selected nodes.*\n\n###### How it's computed:\n*Highest validated ledger sequence minus the lowest, across the selected nodes.*\n\n###### Reading it:\n*0 means every node agrees on the same validated ledger; larger means they diverge.*\n\n###### Healthy range:\n*0 to 1 ledger in steady state.*\n\n###### Watch for:\n*A sustained spread above a few ledgers means some nodes are lagging or the fleet is diverging.*\n\n###### Keywords:\n- **Validated ledger** *(network-wide)* \u2014 a ledger confirmed final by the trusted validator quorum; its contents never change.\n\n###### Computation boundary:\n*Result: Across the selected nodes \u2014 the query aggregates instances into one series.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerServerInfoGauge`\n\n###### References:\n[Validated ledger](https://xrpl.org/docs/concepts/ledgers/open-closed-validated-ledgers) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#validated-ledger)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "yellow", + "value": 2 + }, + { + "color": "red", + "value": 5 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "service_instance_id" + }, + "properties": [ + { + "id": "displayName", + "value": "Node" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Value" + }, + "properties": [ + { + "id": "displayName", + "value": "Seq" + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 209 + }, + "id": 58, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "app": "grafana-assistant-app", + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "max(server_info{metric=\"validated_ledger_seq\",service_name=~\"$service_name\",service_instance_id=~\"$node\",xrpl_network_type=~\"$xrpl_network_type\",xrpl_branch=~\"$xrpl_branch\",xrpl_node_role=~\"$xrpl_node_role\",deployment_environment=~\"$deployment_environment\",xrpl_work_item=~\"$xrpl_work_item\"}) - min(server_info{metric=\"validated_ledger_seq\",service_name=~\"$service_name\",service_instance_id=~\"$node\",xrpl_network_type=~\"$xrpl_network_type\",xrpl_branch=~\"$xrpl_branch\",xrpl_node_role=~\"$xrpl_node_role\",deployment_environment=~\"$deployment_environment\",xrpl_work_item=~\"$xrpl_work_item\"})", + "instant": true, + "legendFormat": "Spread", + "queryType": "instant", + "range": false, + "refId": "A" + } + ], + "title": "Validated Ledger Seq \u2014 Convergence (Max \u2212 Min)", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*How far each node's validated ledger lags behind the network tip, in ledgers.*\n\n###### How it's computed:\n*Highest validated ledger sequence across the selected nodes, minus each node's own sequence.*\n\n###### Reading it:\n*0 means the node is at the tip; larger values mean it trails further behind.*\n\n###### Healthy range:\n*0 to 1 ledger on a synced node.*\n\n###### Watch for:\n*A node stuck at a growing value is falling behind and not keeping up with consensus.*\n\n###### Keywords:\n- **Validated ledger** *(network-wide)* \u2014 a ledger confirmed final by the trusted validator quorum; its contents never change.\n- **Consensus** *(network event)* \u2014 the protocol by which validators agree on the next ledger's transaction set and close time.\n\n###### Computation boundary:\n*Result: Across the selected nodes \u2014 the query aggregates instances into one series.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerServerInfoGauge`\n\n###### References:\n[Validated ledger](https://xrpl.org/docs/concepts/ledgers/open-closed-validated-ledgers) \u00b7 [Consensus](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#validated-ledger)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "none" + } + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 209 + }, + "id": 56, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value_and_name", + "wideLayout": true, + "tooltip": {} + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "app": "grafana-assistant-app", + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sort_desc(scalar(max(server_info{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"validated_ledger_seq\"})) - server_info{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"validated_ledger_seq\"})", + "instant": true, + "legendFormat": "{{service_instance_id}}", + "queryType": "instant", + "range": false, + "refId": "A" + } + ], + "title": "Validated Ledger Seq \u2014 Current (Stat)", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*The running server's build version string.*\n\n###### How it's computed:\n*Read from the version label of the build-info metric (its value is always 1).*\n\n###### Reading it:\n*Confirms which version each node is running.*\n\n###### Healthy range:\n*The expected release version across all nodes.*\n\n###### Watch for:\n*A node on an unexpected or mismatched version in a fleet.*\n\n###### Keywords:\n- **Build version** *(per node)* \u2014 the xrpld release the process is running.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerBuildInfoGauge`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md)", + "fieldConfig": { + "defaults": { + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + } + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 0, + "y": 216 + }, + "id": 102, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "name", + "wideLayout": true, + "tooltip": {} + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "app": "grafana-assistant-app", + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "count by (version) (build_info{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"})", + "instant": false, + "legendFormat": "{{version}}", + "queryType": "range", + "range": true, + "refId": "A" + } + ], + "title": "Build Version", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*Proposer count and convergence time from the last closed consensus round.*\n\n###### How it's computed:\n*Current values of the last-close proposer-count and convergence-time gauges, plotted as lines.*\n\n###### Reading it:\n*A healthy proposer count with a low convergence time is good.*\n\n###### Healthy range:\n*Convergence time of a few seconds with the expected number of proposers.*\n\n###### Watch for:\n*A falling proposer count or rising convergence time signals degrading consensus conditions.*\n\n###### Keywords:\n- **Proposers** *(network event)* \u2014 the count of validators whose proposals this node heard in the last closed round.\n- **Convergence time** *(network event)* \u2014 the wall-clock time the network took to agree a ledger in a round.\n- **Consensus round** *(network event)* \u2014 one propose-and-revise iteration of consensus; several may run before validators converge on a ledger.\n- **Consensus** *(network event)* \u2014 the protocol by which validators agree on the next ledger's transaction set and close time.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerServerInfoGauge`\n\n###### References:\n[Proposers](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#proposers)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "ms", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": 1800000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "ms" + } + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 12, + "y": 216 + }, + "id": 105, + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(server_info{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"last_close_converge_time_ms\"}, \"series\", \"Converge Time ms\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" + } + ], + "title": "Last Close \u2014 Converge Time", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*Proposer count and convergence time from the last closed consensus round.*\n\n###### How it's computed:\n*Current values of the last-close proposer-count and convergence-time gauges, plotted as lines.*\n\n###### Reading it:\n*A healthy proposer count with a low convergence time is good.*\n\n###### Healthy range:\n*Convergence time of a few seconds with the expected number of proposers.*\n\n###### Watch for:\n*A falling proposer count or rising convergence time signals degrading consensus conditions.*\n\n###### Keywords:\n- **Proposers** *(network event)* \u2014 the count of validators whose proposals this node heard in the last closed round.\n- **Convergence time** *(network event)* \u2014 the wall-clock time the network took to agree a ledger in a round.\n- **Consensus round** *(network event)* \u2014 one propose-and-revise iteration of consensus; several may run before validators converge on a ledger.\n- **Consensus** *(network event)* \u2014 the protocol by which validators agree on the next ledger's transaction set and close time.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerServerInfoGauge`\n\n###### References:\n[Proposers](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#proposers)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Proposers", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": 1800000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "short" + } + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 0, + "y": 228 + }, + "id": 104, + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(server_info{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"last_close_proposers\"}, \"series\", \"Proposers\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" + } + ], + "title": "Last Close \u2014 Proposers", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*The wall-clock time between consecutive ledger closes \u2014 the network close cadence.*\n\n###### How it's computed:\n*Close Interval: 1 / rate(ledgers_closed_total), the average seconds between closes from the monotonic close counter (scrape-independent, unlike a gauge delta which would alias to the scrape period). Last-Close Age: time() minus the last-close network time (server_info last_close_time gauge + Ripple-epoch offset), i.e. seconds since the last ledger closed.*\n\n###### Reading it:\n*A steady line near the network's target close interval.*\n\n###### Healthy range:\n*About 3-5s on mainnet; workload-dependent on test networks.*\n\n###### Watch for:\n*A rising interval (consensus slowing or the node lagging) or a flat line at zero (ledgers no longer closing).*\n\n###### Keywords:\n- **Ledger close** *(network event)* \u2014 the current open ledger is closed and a new closed ledger is built from the agreed transaction set.\n- **Ledger close interval** *(network-wide)* \u2014 the network's steady ledger rhythm \u2014 roughly one closed ledger every 3-5 seconds on Mainnet.\n- **Consensus** *(network event)* \u2014 the protocol by which validators agree on the next ledger's transaction set and close time.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp) \u00b7 [RCLConsensus.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/consensus/RCLConsensus.cpp)\n\n###### Function:\n`registerServerInfoGauge (last_close_time) ; ledgers_closed_total`\n\n###### References:\n[Ledger close](https://xrpl.org/docs/concepts/ledgers/open-closed-validated-ledgers) \u00b7 [Ledger close interval](https://xrpl.org/docs/concepts/ledgers/ledger-close-times) \u00b7 [Consensus](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#ledger-close)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Seconds", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": 1800000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "s" + }, + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": "/^Last-Close Age/" + }, + "properties": [ + { + "id": "unit", + "value": "s" + }, + { + "id": "custom.axisLabel", + "value": "Age Since Last Close (Sec)" + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": "/^Close Interval/" + }, + "properties": [ + { + "id": "unit", + "value": "s" + }, + { + "id": "custom.axisPlacement", + "value": "right" + }, + { + "id": "custom.axisLabel", + "value": "Close Interval (Sec)" + } + ] + } + ] + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 12, + "y": 228 + }, + "id": 128, + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(time() - (server_info{metric=\"last_close_time\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"} + 946684800), \"series\", \"Last-Close Age (s)\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(1 / sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(ledgers_closed_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])), \"series\", \"Close Interval\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "B" + } + ], + "title": "Ledger Close Interval & Age", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*Cumulative object-payload bytes this process has written to the NodeStore back end. This is not the size of the store on disk.*\n\n###### How it's computed:\n*Current value of the stored_object_bytes gauge, plotted over time. It observes getStoreSize(), the same accessor node_written_bytes uses, so the two series are equal and their ratio is a constant 1.0 rather than a write-amplification measure.*\n\n###### Reading it:\n*A smoothly growing line is normal; the slope is the write rate. It excludes NuDB's keys, bucket padding and log, and it restarts from zero with the process while the files on disk do not.*\n\n###### Healthy range:\n*Gradual growth consistent with ledger data being stored.*\n\n###### Watch for:\n*A sudden jump in growth rate can indicate runaway storage or an unexpected back-fill. Do not use this panel to size the store on disk or to plan disk capacity; no metric reports on-disk size today, so check the filesystem directly.*\n\n###### Keywords:\n- **NuDB** *(per node)* \u2014 the append-only key-value database used as the default NodeStore backend.\n- **NodeStore** *(per node)* \u2014 the key-value object store holding ledger data (tree nodes), backed by NuDB.\n- **Back-fill / catch-up** *(per node)* \u2014 fetching missing historical ledgers from peers to fill gaps or reach the network tip.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerStorageDetailGauge`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#nudb)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Stored Bytes", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": 1800000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "decbytes" + } + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 0, + "y": 240 + }, + "id": 106, + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(storage_detail{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"stored_object_bytes\"}, \"series\", \"NuDB Stored Bytes\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" + } + ], + "title": "Stored Object Bytes", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*Live instance counts for the busiest internal object types.*\n\n###### How it's computed:\n*Current per-type instance counts, showing the top 15 types over time.*\n\n###### Reading it:\n*Stable lines are healthy; each line is one object type's live count.*\n\n###### Healthy range:\n*Steady counts that rise and fall with load.*\n\n###### Watch for:\n*A single type climbing without bound suggests memory pressure or a leak.*\n\n###### Keywords:\n- **Object instance count** *(per node)* \u2014 live in-memory instances of a tracked C++ type, used to spot leaks.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerObjectCountGauge`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Instances", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "bars", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 0, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": 1800000, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + } + }, + "gridPos": { + "h": 16, + "w": 24, + "x": 0, + "y": 252 + }, + "id": 107, + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": ["last", "max"], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(topk(15, object_count{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", type=~\"$type\"}), \"series\", \"$1\", \"type\", \"(.*)\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" + } + ], + "title": "Object Instance Counts", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 268 + }, + "id": 126, + "title": "Complete Ledgers & DB", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*The contiguous ranges of ledgers the node holds locally.*\n\n###### How it's computed:\n*Current start and end bounds of each complete range, listed as table rows.*\n\n###### Reading it:\n*Fewer ranges is better; one continuous range means an unbroken history.*\n\n###### Healthy range:\n*A single range covering the configured retention window.*\n\n###### Watch for:\n*Many fragmented ranges indicate gaps in stored history from missed or failed fetches.*\n\n###### Keywords:\n- **Complete ledger ranges** *(per node)* \u2014 the contiguous spans of ledgers the node holds locally; one unbroken range is ideal.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerCompleteLedgersGauge`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#complete-ledger-ranges)", + "fieldConfig": { + "defaults": { + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "footer": { + "reducers": [] + }, + "inspect": false + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + } + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 0, + "y": 269 + }, + "id": 109, + "options": { + "cellHeight": "sm", + "showHeader": true, + "tooltip": {} + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "complete_ledgers{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}", + "format": "table", + "instant": true, + "legendFormat": "{{bound}} [range {{index}}] [{{service_instance_id}}]", + "refId": "A" + } + ], + "title": "Complete Ledger Ranges", + "type": "table" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*Sizes of the relational databases in KB (total, ledger, transaction).*\n\n###### How it's computed:\n*Current values of the per-database size gauges, plotted as lines.*\n\n###### Reading it:\n*Smoothly growing lines are normal; the split shows where storage is used.*\n\n###### Healthy range:\n*Gradual growth consistent with retained history.*\n\n###### Watch for:\n*An abrupt change in growth rate can indicate storage pressure or a pruning issue.*\n\n###### Keywords:\n- **NodeStore** *(per node)* \u2014 the key-value object store holding ledger data (tree nodes), backed by NuDB.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerDbMetricsGauge`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#nodestore)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Size (KB)", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": 1800000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "deckbytes" + } + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 12, + "y": 269 + }, + "id": 110, + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(db_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"db_kb_total\"}, \"series\", \"Total KB\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(db_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"db_kb_ledger\"}, \"series\", \"Ledger KB\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(db_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"db_kb_transaction\"}, \"series\", \"Transaction KB\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "C" + } + ], + "title": "Database Sizes", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*How many historical ledgers the node is back-filling per minute.*\n\n###### How it's computed:\n*Current value of the historical-fetch-per-minute gauge.*\n\n###### Reading it:\n*Near zero once history is complete; elevated while back-filling.*\n\n###### Healthy range:\n*Close to zero in steady state.*\n\n###### Watch for:\n*A sustained high rate means the node is still filling gaps in its stored history.*\n\n###### Keywords:\n- **Historical fetch rate** *(per node)* \u2014 how many historical ledgers the node is back-filling per minute.\n- **Back-fill / catch-up** *(per node)* \u2014 fetching missing historical ledgers from peers to fill gaps or reach the network tip.\n- **Complete ledger ranges** *(per node)* \u2014 the contiguous spans of ledgers the node holds locally; one unbroken range is ideal.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerDbMetricsGauge`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#historical-fetch-rate)", + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "suffix: fetches/min" + } + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 0, + "y": 281 + }, + "id": 111, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + }, + "wideLayout": true + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(db_metrics{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"historical_perminute\"}, \"series\", \"Fetches/min\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" + } + ], + "title": "Historical Fetch Rate", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*Cumulative count of peers disconnected for exceeding resource limits.*\n\n###### How it's computed:\n*Current value of the resource-disconnect gauge, plotted over time.*\n\n###### Reading it:\n*A flat line is healthy; steps up mean peers were dropped for overuse.*\n\n###### Healthy range:\n*Flat or very slowly rising.*\n\n###### Watch for:\n*A rising line indicates peers are being throttled off, consistent with abusive or misbehaving peers.*\n\n###### Keywords:\n- **Resource disconnect** *(per node)* \u2014 a peer dropped for exceeding resource/load limits \u2014 the node shedding abusive or overactive peers.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerServerInfoGauge`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#resource-disconnect)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": 1800000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + } + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 12, + "y": 281 + }, + "id": 112, + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(server_info{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", metric=\"peer_disconnects_resources\"}, \"series\", \"Resource Disconnects\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" + } + ], + "title": "Peer Disconnects (Resources)", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 293 + }, + "id": 127, + "title": "Ledger Economy", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*The node's local load-based fee factor that scales its minimum transaction cost; baseline 256 at idle.*\n\n###### How it's computed:\n*Current value of the local load-fee economy gauge.*\n\n###### Reading it:\n*Steady at the baseline (256) is normal; higher values mean the node is raising its fee in response to load.*\n\n###### Healthy range:\n*Around 256 (the normal baseline) when idle.*\n\n###### Watch for:\n*A climbing factor, which indicates the node is under transaction load pressure.*\n\n###### Keywords:\n- **Load factor** *(per node)* \u2014 a unitless multiplier (1.0 = no load) that scales the base transaction cost as the node comes under load.\n- **Base fee** *(network-wide)* \u2014 the baseline transaction cost for a reference transaction under minimum load, in drops.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerLedgerEconomyGauge`\n\n###### References:\n[Load factor](https://xrpl.org/docs/concepts/transactions/transaction-cost#local-load-cost) \u00b7 [Base fee](https://xrpl.org/docs/concepts/transactions/transaction-cost) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#load-factor)", + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "si:drops" + } + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 0, + "y": 294 + }, + "id": 114, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + }, + "wideLayout": true + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(ledger_economy{metric=\"base_fee_xrp\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Base Fee\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" + } + ], + "title": "Base Fee (drops)", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*The minimum XRP balance required to keep an account on the ledger, in drops.*\n\n###### How it's computed:\n*Current value of the reserve-base economy gauge.*\n\n###### Reading it:\n*A stable value is expected; it changes only via network amendment or vote.*\n\n###### Healthy range:\n*The configured network reserve base.*\n\n###### Watch for:\n*An unexpected change outside a known amendment or fee vote.*\n\n###### Keywords:\n- **drops** *(network-wide)* \u2014 the smallest XRP unit \u2014 one drop is 0.000001 XRP (one millionth).\n- **Reserve (base & owner)** *(network-wide)* \u2014 the minimum XRP an account must hold \u2014 a base reserve plus an increment per owned ledger object.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerLedgerEconomyGauge`\n\n###### References:\n[drops](https://xrpl.org/docs/references/protocol/data-types/basic-data-types#specifying-currency-amounts) \u00b7 [Reserve (base & owner)](https://xrpl.org/docs/concepts/accounts/reserves) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#drops)", + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "si:drops" + } + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 12, + "y": 294 + }, + "id": 115, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + }, + "wideLayout": true + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(ledger_economy{metric=\"reserve_base_xrp\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Reserve Base\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" + } + ], + "title": "Reserve Base (drops)", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*The additional XRP reserve required per owned ledger object, in drops.*\n\n###### How it's computed:\n*Current value of the reserve-increment economy gauge.*\n\n###### Reading it:\n*A stable value is expected; it changes only via network amendment or vote.*\n\n###### Healthy range:\n*The configured network owner reserve increment.*\n\n###### Watch for:\n*An unexpected change outside a known amendment or fee vote.*\n\n###### Keywords:\n- **Reserve (base & owner)** *(network-wide)* \u2014 the minimum XRP an account must hold \u2014 a base reserve plus an increment per owned ledger object.\n- **drops** *(network-wide)* \u2014 the smallest XRP unit \u2014 one drop is 0.000001 XRP (one millionth).\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerLedgerEconomyGauge`\n\n###### References:\n[Reserve (base & owner)](https://xrpl.org/docs/concepts/accounts/reserves) \u00b7 [drops](https://xrpl.org/docs/references/protocol/data-types/basic-data-types#specifying-currency-amounts) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#reserve-base-owner)", + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "si:drops" + } + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 0, + "y": 306 + }, + "id": 116, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + }, + "wideLayout": true + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(ledger_economy{metric=\"reserve_inc_xrp\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Reserve Inc\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" + } + ], + "title": "Reserve Inc (drops)", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*Seconds since the last validated ledger closed, plotted over time.*\n\n###### How it's computed:\n*Current value of the ledger-age economy gauge, sampled each interval.*\n\n###### Reading it:\n*Lower is better; it should stay near the ledger close interval. Mirrors the Validated Ledger Age panel.*\n\n###### Healthy range:\n*Under about 10 seconds.*\n\n###### Watch for:\n*Growth beyond the expected close interval, meaning the node is not keeping up with validated ledgers.*\n\n###### Keywords:\n- **Validated ledger** *(network-wide)* \u2014 a ledger confirmed final by the trusted validator quorum; its contents never change.\n- **Ledger close interval** *(network-wide)* \u2014 the network's steady ledger rhythm \u2014 roughly one closed ledger every 3-5 seconds on Mainnet.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerLedgerEconomyGauge`\n\n###### References:\n[Validated ledger](https://xrpl.org/docs/concepts/ledgers/open-closed-validated-ledgers) \u00b7 [Ledger close interval](https://xrpl.org/docs/concepts/ledgers/ledger-close-times) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#validated-ledger)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Ledger Age (s)", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 3, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": 1800000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "line" + } + }, + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "yellow", + "value": 10 + }, + { + "color": "red", + "value": 20 + } + ] + }, + "unit": "s" + } + }, + "gridPos": { + "h": 16, + "w": 12, + "x": 12, + "y": 306 + }, + "id": 117, + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(ledger_economy{metric=\"ledger_age_seconds\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Ledger Age\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" + } + ], + "title": "Ledger Age", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*The network transaction throughput reported by the ledger economy metrics.*\n\n###### How it's computed:\n*Current value of the transaction-rate economy gauge, plotted over time.*\n\n###### Reading it:\n*Reflects how many transactions are being processed; higher means busier.*\n\n###### Healthy range:\n*Workload-dependent.*\n\n###### Watch for:\n*A sudden sustained surge can indicate a transaction flood; a drop to zero can indicate the node stopped processing.*\n\n###### Keywords:\n- **Transaction type** *(network-wide)* \u2014 the kind of transaction (Payment, OfferCreate, TrustSet, AMM*, NFToken*, etc.), used as a breakdown dimension.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerLedgerEconomyGauge`\n\n###### References:\n[Transaction type](https://xrpl.org/docs/references/protocol/transactions/types) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#transaction-type)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Transactions / Sec", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 3, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": 1800000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "suffix: transactions/s" + } + }, + "gridPos": { + "h": 16, + "w": 24, + "x": 0, + "y": 322 + }, + "id": 118, + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(ledger_economy{metric=\"transaction_rate\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Tx Rate\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" + } + ], + "title": "Transaction Rate", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*Time to fetch a missing ledger from peers, at the 95th percentile.*\n\n###### How it's computed:\n*Per-acquire durations aggregated to their 95th percentile per node over a 5-minute window.*\n\n###### Reading it:\n*Lower is better; populated mainly during sync or back-fill.*\n\n###### Healthy range:\n*Low when synced; higher and more active while catching up.*\n\n###### Watch for:\n*A spike signals the node is falling behind or recovering from a fork.*\n\n###### Keywords:\n- **Ledger acquire (inbound fetch)** *(per node)* \u2014 fetching a specific missing ledger from peers; tracked by duration and outcome (complete/failed).\n- **Back-fill / catch-up** *(per node)* \u2014 fetching missing historical ledgers from peers to fill gaps or reach the network tip.\n- **Fork** *(network-wide)* \u2014 when nodes validate divergent ledger chains instead of a single agreed history.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[InboundLedger.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/detail/InboundLedger.cpp)\n\n###### Function:\n`InboundLedger::init`\n\n###### References:\n[Fork](https://xrpl.org/docs/concepts/consensus-protocol) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#ledger-acquire-inbound-fetch)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Duration (ms)", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 3, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": 1800000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ms" + } + }, + "gridPos": { + "h": 16, + "w": 12, + "x": 0, + "y": 338 + }, + "id": 119, + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(histogram_quantile(0.95, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_duration_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"ledger.acquire\"}[5m]))), \"series\", \"P95 Acquire\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" + } + ], + "title": "Ledger Acquire Duration (Inbound Fetch)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "###### What this is:\n*Rate of completed ledger fetches split by outcome (complete or failed).*\n\n###### How it's computed:\n*Per-second rate of finished acquisitions grouped by outcome, per node, over a 5-minute window.*\n\n###### Reading it:\n*Complete should dominate; the failed line should stay near zero.*\n\n###### Healthy range:\n*Complete tracking fetch demand, failed near zero.*\n\n###### Watch for:\n*A rising failed rate means the node cannot fetch needed ledgers from its peers.*\n\n###### Keywords:\n- **Ledger acquire (inbound fetch)** *(per node)* \u2014 fetching a specific missing ledger from peers; tracked by duration and outcome (complete/failed).\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[InboundLedger.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/detail/InboundLedger.cpp)\n\n###### Function:\n`InboundLedger::init`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#ledger-acquire-inbound-fetch)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Acquisitions / Sec", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 3, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": 1800000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "suffix: acquisitions/s" + } + }, + "gridPos": { + "h": 16, + "w": 12, + "x": 12, + "y": 338 + }, + "id": 120, + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "13.2.0-28926505616", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(sum by (outcome, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"ledger.acquire\"}[$__rate_interval])), \"series\", \"$1\", \"outcome\", \"(.*)\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")", + "refId": "A" + } + ], + "title": "Ledger Acquire Rate by Outcome", + "type": "timeseries" }, { "title": "Job Queue Concurrency Limits", @@ -2522,20 +5146,20 @@ "h": 1, "w": 24, "x": 0, - "y": 219 + "y": 354 }, "collapsed": false, "panels": [] }, { "title": "Job Queue Saturation (Running vs Limit)", - "description": "###### What this is:\n*How close each concurrency-capped job type is to its ceiling. JobQueue enforces a per-type limit on how many jobs of that type may run at once, and the tight ones carry ledger-sync traffic: makeFetchPack 1, ledgerRequest 3, ledgerData 3, updatePaths 1, fetchTxnData 5. A type at its ceiling cannot start more work no matter how many workers are idle, so this is a different kind of limit from CPU or disk.*\n\n###### How it's computed:\n*Each jobq__running gauge divided by that type's own limit from JobTypes.h, so every line shares one 0-to-1 axis. 1.0 means running equals the limit. Multiply a reading by the limit shown in its legend to recover the raw job count. The gauges are published by JobQueue::collect() under the mutex that guards the counters.*\n\n###### Reading it:\n*Read the distance to 1.0, not the absolute height. Below 1.0 the type has spare slots and its queue wait is not the limit's fault. Touching 1.0 briefly is normal work. Sitting at 1.0 means the type is pinned at its ceiling and every further job of that type is being deferred rather than started, which is what turns into queue wait downstream. Because the limits differ, a raw count of 3 is saturation for ledgerRequest but only 60 percent for fetchTxnData; normalising is what makes the lines comparable.*\n\n###### Healthy range:\n*Below 1.0, with brief touches under load.*\n\n###### Watch for:\n*A line flat at 1.0: that type is the binding constraint. ledgerRequest pinned means the 3 slots shared by RcvGetLedger and RcvGetObjByHash are full, so peer ledger and object requests are queueing behind each other; the Ledger Data and Sync dashboard splits that wait by handler and shows the matching deferred depth. ledgerData or fetchTxnData pinned means inbound ledger data cannot be absorbed and validated ledger age will grow. makeFetchPack or updatePaths pinned at their limit of 1 means a single long job is blocking the whole type. Note the collector samples once per interval, so a line that never reaches 1.0 is not proof the type was never momentarily saturated.*\n\n###### Source:\n[core/JobQueue.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/core/detail/JobQueue.cpp)\n\n###### Function:\n`JobQueue::getNextJob (limit enforcement) / JobQueue::collect (publication)`", + "description": "###### What this is:\n*How close each concurrency-capped job type is to its ceiling. JobQueue enforces a per-type limit on how many jobs of that type may run at once, and the tight ones carry ledger-sync traffic: makeFetchPack 1, ledgerRequest 3, ledgerData 3, updatePaths 1, fetchTxnData 5. A type at its ceiling cannot start more work no matter how many workers are idle, so this is a different kind of limit from CPU or disk.*\n\n###### How it's computed:\n*Each jobq__running gauge divided by that type's own limit from JobTypes.h, so every line shares one 0-to-1 axis. 1.0 means running equals the limit. Multiply a reading by the limit shown in its legend to recover the raw job count. JobQueue::collect snapshots all three per-type counters under the queue's own lock and publishes them after releasing it, on the 1-second export cycle.*\n\n###### Reading it:\n*Read the distance to 1.0, not the absolute height. Below 1.0 the type has spare slots and its queue wait is not the limit's fault. Touching 1.0 briefly is normal work. Sitting at 1.0 means the type is pinned at its ceiling and every further job of that type is being deferred rather than started, which is what turns into queue wait downstream. Because the limits differ, a raw count of 3 is saturation for ledgerRequest but only 60 percent for fetchTxnData; normalizing is what makes the lines comparable.*\n\n###### Healthy range:\n*Below 1.0, with brief touches under load.*\n\n###### Watch for:\n*A line flat at 1.0: that type is the binding constraint. ledgerRequest pinned means the 3 slots shared by RcvGetLedger and RcvGetObjByHash are full, so peer ledger and object requests are queueing behind each other; the Ledger Data and Sync dashboard splits that wait by handler and shows the matching deferred depth. ledgerData or fetchTxnData pinned means inbound ledger data cannot be absorbed and validated ledger age will grow. makeFetchPack or updatePaths pinned at their limit of 1 means a single long job is blocking the whole type. These are sampled gauges, so a line that never reaches 1.0 is not proof the type was never momentarily saturated.*\n\n###### Keywords:\n- **Job queue / job type** *(per node)* \u2014 xrpld's worker-thread pool; every unit of background work is enqueued under a named job type.\n- **Concurrency limit** *(per node)* \u2014 the cap on how many jobs of one type may run at once; a type at its cap cannot start more work.\n- **Deferred job** *(per node)* \u2014 a job held back because its type is already at its concurrency limit; the leading indicator of queue backpressure.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[core/JobQueue.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/core/detail/JobQueue.cpp)\n\n###### Function:\n`JobQueue::getNextJob (limit enforcement) / JobQueue::collect (publication)`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#concurrency-limit)", "type": "timeseries", "gridPos": { "h": 8, "w": 24, "x": 0, - "y": 220 + "y": 355 }, "options": { "tooltip": { @@ -2544,9 +5168,12 @@ "sort": "desc" }, "legend": { - "displayMode": "table", - "placement": "right", - "calcs": ["mean", "max"] + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true } }, "targets": [ @@ -2554,36 +5181,36 @@ "datasource": { "type": "prometheus" }, - "expr": "label_replace(jobq_makefetchpack_running{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"} / 1, \"series\", \"makeFetchPack (Limit 1)\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(jobq_makefetchpack_running{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"} / 1, \"series\", \"makeFetchPack (Limit 1)\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { "type": "prometheus" }, - "expr": "label_replace(jobq_ledgerrequest_running{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"} / 3, \"series\", \"ledgerRequest (Limit 3)\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(jobq_ledgerrequest_running{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"} / 3, \"series\", \"ledgerRequest (Limit 3)\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { "type": "prometheus" }, - "expr": "label_replace(jobq_ledgerdata_running{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"} / 3, \"series\", \"ledgerData (Limit 3)\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(jobq_ledgerdata_running{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"} / 3, \"series\", \"ledgerData (Limit 3)\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { "type": "prometheus" }, - "expr": "label_replace(jobq_updatepaths_running{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"} / 1, \"series\", \"updatePaths (Limit 1)\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(jobq_updatepaths_running{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"} / 1, \"series\", \"updatePaths (Limit 1)\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { "type": "prometheus" }, - "expr": "label_replace(jobq_fetchtxndata_running{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"} / 5, \"series\", \"fetchTxnData (Limit 5)\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(jobq_fetchtxndata_running{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"} / 5, \"series\", \"fetchTxnData (Limit 5)\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "percentunit", "max": 1, "min": 0, @@ -2625,6 +5252,19 @@ "tags": ["node", "health"], "templating": { "list": [ + { + "name": "DS_PROMETHEUS", + "type": "datasource", + "label": "Prometheus", + "query": "prometheus", + "regex": "", + "current": {}, + "hide": 0, + "refresh": 1, + "includeAll": false, + "multi": false, + "options": [] + }, { "name": "service_name", "label": "Service Name", @@ -2633,7 +5273,7 @@ "query": "label_values(service_name)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -2653,7 +5293,7 @@ "query": "label_values(deployment_environment)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -2673,7 +5313,7 @@ "query": "label_values(xrpl_network_type)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -2753,7 +5393,7 @@ "query": "label_values(target_info, service_instance_id)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -2773,7 +5413,7 @@ "query": "label_values(object_count, type)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -2827,5 +5467,5 @@ }, "title": "Node Health", "uid": "node-health", - "refresh": "5s" + "refresh": "30s" } diff --git a/docker/telemetry/grafana/dashboards/overlay-traffic-detail.json b/docker/telemetry/grafana/dashboards/overlay-traffic-detail.json index fa812e425a..b21a017f81 100644 --- a/docker/telemetry/grafana/dashboards/overlay-traffic-detail.json +++ b/docker/telemetry/grafana/dashboards/overlay-traffic-detail.json @@ -1,8 +1,38 @@ { "annotations": { - "list": [] + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + }, + { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": false, + "iconColor": "rgb(70, 70, 70)", + "name": "Annotate perf-iac runs", + "target": { + "limit": 100, + "matchAny": false, + "tags": ["perf-iac"], + "type": "tags" + }, + "type": "tags" + } + ] }, - "description": "What this shows: Fine-grained breakdown of peer-to-peer overlay traffic beyond the main network view: squelch relay control, protocol overhead, validator-list distribution, transaction-set exchange, transaction availability, ledger-proof and replay traffic, and unclassified messages.\nUse it to: Drill into individual overlay message categories to diagnose relay efficiency, overhead, and catch-up traffic.", + "description": "What this shows: Fine-grained breakdown of peer-to-peer overlay traffic beyond the main network view: squelch relay control, protocol overhead, validator-list distribution, transaction-set exchange, transaction availability, ledger-proof and replay traffic, and unclassified messages. \u2014 Use it to: Drill into individual overlay message categories to diagnose relay efficiency, overhead, and catch-up traffic.", "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 1, @@ -11,7 +41,7 @@ "panels": [ { "title": "Squelch Traffic (Messages)", - "description": "###### What this is:\n*Squelch relay-control messages in/out, plus messages suppressed by squelch and squelch directives that were ignored. Squelch reduces redundant message forwarding between peers.*\n\n###### How it's computed:\n*Per-second message rate for the squelch, squelch-suppressed, and squelch-ignored categories, in and out.*\n\n###### Reading it:\n*High suppressed counts mean squelch is saving bandwidth; ignored should stay low.*\n\n###### Healthy range:\n*workload-dependent; suppressed far above ignored.*\n\n###### Watch for:\n*High ignored counts (peers not honoring squelch) or squelch traffic itself dominating.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`", + "description": "###### What this is:\n*Squelch relay-control messages in/out, plus messages suppressed by squelch and squelch directives that were ignored. Squelch reduces redundant message forwarding between peers.*\n\n###### How it's computed:\n*Per-second message rate for the squelch, squelch-suppressed, and squelch-ignored categories, in and out.*\n\n###### Reading it:\n*High suppressed counts mean squelch is saving bandwidth; ignored should stay low.*\n\n###### Healthy range:\n*workload-dependent; suppressed far above ignored.*\n\n###### Watch for:\n*High ignored counts (peers not honoring squelch) or squelch traffic itself dominating.*\n\n###### Keywords:\n- **Transaction suppression** *(per node)* \u2014 dropping a transaction already seen from another peer, so it is not reprocessed.\n- **Squelch** *(per node)* \u2014 control messages that tell a peer to stop forwarding a given validator's messages, cutting redundancy.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#transaction-suppression)", "type": "timeseries", "gridPos": { "h": 8, @@ -29,44 +59,50 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(squelch_messages_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Squelch In\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(squelch_messages_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Squelch In\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(squelch_messages_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Squelch Out\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(squelch_messages_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Squelch Out\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(squelch_suppressed_messages_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Suppressed In\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(squelch_suppressed_messages_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Suppressed In\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(squelch_suppressed_messages_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Suppressed Out\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(squelch_suppressed_messages_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Suppressed Out\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(squelch_ignored_messages_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Ignored In\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(squelch_ignored_messages_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Ignored In\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(squelch_ignored_messages_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Ignored Out\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(squelch_ignored_messages_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Ignored Out\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: messages/s", "custom": { "axisLabel": "Messages / Sec", @@ -81,7 +117,7 @@ }, { "title": "Overhead Traffic Breakdown (Bytes)", - "description": "###### What this is:\n*Overlay protocol overhead bytes split into base overhead, intra-cluster overhead, and validator-manifest distribution overhead.*\n\n###### How it's computed:\n*Per-second in/out byte rate for the overhead, overhead-cluster, and overhead-manifest categories.*\n\n###### Reading it:\n*Base overhead is routine; cluster and manifest rise around cluster syncs and manifest changes.*\n\n###### Healthy range:\n*workload-dependent; low and stable.*\n\n###### Watch for:\n*Sustained high cluster or manifest overhead (frequent cluster state churn or manifest reissue).*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`", + "description": "###### What this is:\n*Overlay protocol overhead bytes split into base overhead, intra-cluster overhead, and validator-manifest distribution overhead.*\n\n###### How it's computed:\n*Per-second in/out byte rate for the overhead, overhead-cluster, and overhead-manifest categories.*\n\n###### Reading it:\n*Base overhead is routine; cluster and manifest rise around cluster syncs and manifest changes.*\n\n###### Healthy range:\n*workload-dependent; low and stable.*\n\n###### Watch for:\n*Sustained high cluster or manifest overhead (frequent cluster state churn or manifest reissue).*\n\n###### Keywords:\n- **Overlay** *(per node)* \u2014 the peer-to-peer network layer over which nodes exchange transactions, proposals, and validations.\n- **Manifest** *(network-wide)* \u2014 a signed record binding a validator's rotating signing key to its stable master key.\n- **Cluster** *(cluster-wide)* \u2014 a group of trusted co-operated nodes that share load information and skip some verification.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`\n\n###### References:\n[Overlay](https://xrpl.org/docs/concepts/networks-and-servers/peer-protocol) \u00b7 [Cluster](https://xrpl.org/docs/concepts/networks-and-servers/clustering) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#overlay)", "type": "timeseries", "gridPos": { "h": 8, @@ -99,44 +135,50 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(overhead_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Base Overhead In\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(overhead_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Base Overhead In\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(overhead_bytes_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Base Overhead Out\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(overhead_bytes_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Base Overhead Out\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(overhead_cluster_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Cluster In\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(overhead_cluster_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Cluster In\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(overhead_cluster_bytes_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Cluster Out\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(overhead_cluster_bytes_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Cluster Out\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(overhead_manifest_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Manifest In\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(overhead_manifest_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Manifest In\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(overhead_manifest_bytes_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Manifest Out\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(overhead_manifest_bytes_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Manifest Out\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "Bps", "custom": { "axisLabel": "Bytes", @@ -151,7 +193,7 @@ }, { "title": "Validator List Traffic", - "description": "###### What this is:\n*Bytes and messages exchanged distributing validator lists (trusted-list configuration) between peers.*\n\n###### How it's computed:\n*Per-second in/out byte and message rate for the validator-lists category.*\n\n###### Reading it:\n*Bursts when lists update or new peers connect; quiet otherwise.*\n\n###### Healthy range:\n*workload-dependent; occasional bursts.*\n\n###### Watch for:\n*Continuous high volume (repeated list re-fetching or churn).*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`", + "description": "###### What this is:\n*Bytes and messages exchanged distributing validator lists (trusted-list configuration) between peers.*\n\n###### How it's computed:\n*Per-second in/out byte and message rate for the validator-lists category.*\n\n###### Reading it:\n*Bursts when lists update or new peers connect; quiet otherwise.*\n\n###### Healthy range:\n*workload-dependent; occasional bursts.*\n\n###### Watch for:\n*Continuous high volume (repeated list re-fetching or churn).*\n\n###### Keywords:\n- **Validator list** *(network-wide)* \u2014 signed lists of recommended validators (UNLs) that peers distribute to each other.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`\n\n###### References:\n[Validator list](https://xrpl.org/docs/concepts/consensus-protocol/unl) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#validator-list)", "type": "timeseries", "gridPos": { "h": 8, @@ -169,32 +211,36 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(validator_lists_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Bytes In\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(validator_lists_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Bytes In\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(validator_lists_bytes_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Bytes Out\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(validator_lists_bytes_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Bytes Out\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(validator_lists_messages_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Messages In\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(validator_lists_messages_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Messages In\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(validator_lists_messages_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Messages Out\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(validator_lists_messages_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Messages Out\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: messages/s", "custom": { "axisLabel": "Messages / Sec & Bytes / Sec", @@ -226,7 +272,7 @@ }, { "title": "Set Get/Share Traffic (Bytes)", - "description": "###### What this is:\n*Transaction-set fetch (get) and share bytes exchanged during ledger close.*\n\n###### How it's computed:\n*Per-second in/out byte rate for the set-get and set-share categories.*\n\n###### Reading it:\n*Some exchange each ledger is normal as peers reconcile transaction sets.*\n\n###### Healthy range:\n*workload-dependent.*\n\n###### Watch for:\n*High set-get (peers frequently missing transaction sets: possible sync delays).*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`", + "description": "###### What this is:\n*Transaction-set fetch (get) and share bytes exchanged during ledger close.*\n\n###### How it's computed:\n*Per-second in/out byte rate for the set-get and set-share categories.*\n\n###### Reading it:\n*Some exchange each ledger is normal as peers reconcile transaction sets.*\n\n###### Healthy range:\n*workload-dependent.*\n\n###### Watch for:\n*High set-get (peers frequently missing transaction sets: possible sync delays).*\n\n###### Keywords:\n- **Set get/share** *(per node)* \u2014 exchange of candidate transaction sets between peers as they reconcile during a ledger close.\n- **Ledger close** *(network event)* \u2014 the current open ledger is closed and a new closed ledger is built from the agreed transaction set.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`\n\n###### References:\n[Ledger close](https://xrpl.org/docs/concepts/ledgers/open-closed-validated-ledgers) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#set-get-share)", "type": "timeseries", "gridPos": { "h": 8, @@ -244,32 +290,36 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(set_get_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Set Get In\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(set_get_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Set Get In\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(set_get_bytes_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Set Get Out\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(set_get_bytes_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Set Get Out\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(set_share_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Set Share In\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(set_share_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Set Share In\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(set_share_bytes_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Set Share Out\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(set_share_bytes_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Set Share Out\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "Bps", "custom": { "axisLabel": "Bytes", @@ -284,7 +334,7 @@ }, { "title": "Have/Requested Transactions (Messages)", - "description": "###### What this is:\n*Transaction-availability messages: advertisements that a peer has certain transactions, and explicit requests for transaction data.*\n\n###### How it's computed:\n*Per-second in/out message rate for the have-transactions and requested-transactions categories.*\n\n###### Reading it:\n*Compare requested versus have to gauge how well transactions are propagating.*\n\n###### Healthy range:\n*workload-dependent.*\n\n###### Watch for:\n*Requested far exceeding have (peers behind on transaction propagation).*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`", + "description": "###### What this is:\n*Transaction-availability messages: advertisements that a peer has certain transactions, and explicit requests for transaction data.*\n\n###### How it's computed:\n*Per-second in/out message rate for the have-transactions and requested-transactions categories.*\n\n###### Reading it:\n*Compare requested versus have to gauge how well transactions are propagating.*\n\n###### Healthy range:\n*workload-dependent.*\n\n###### Watch for:\n*Requested far exceeding have (peers behind on transaction propagation).*\n\n###### Keywords:\n- **Have / requested transactions** *(per node)* \u2014 advertisements that a peer holds certain transactions, and explicit requests for transaction data.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#have-requested-transactions)", "type": "timeseries", "gridPos": { "h": 8, @@ -302,32 +352,36 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(have_transactions_messages_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Have TX In\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(have_transactions_messages_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Have TX In\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(have_transactions_messages_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Have TX Out\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(have_transactions_messages_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Have TX Out\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(requested_transactions_messages_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Requested TX In\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(requested_transactions_messages_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Requested TX In\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(requested_transactions_messages_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Requested TX Out\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(requested_transactions_messages_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Requested TX Out\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: messages/s", "custom": { "axisLabel": "Messages / Sec", @@ -342,7 +396,7 @@ }, { "title": "Unknown / Unclassified Traffic", - "description": "###### What this is:\n*Overlay traffic that matches no known message category, in bytes and messages.*\n\n###### How it's computed:\n*Current in/out byte and message counts for the unknown category.*\n\n###### Reading it:\n*Should be at or near zero.*\n\n###### Healthy range:\n*zero.*\n\n###### Watch for:\n*Any sustained non-zero value (protocol version mismatch, corrupted messages, or an unclassified new message type).*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`", + "description": "###### What this is:\n*Overlay traffic that matches no known message category, in bytes and messages.*\n\n###### How it's computed:\n*Current in/out byte and message counts for the unknown category.*\n\n###### Reading it:\n*Should be at or near zero.*\n\n###### Healthy range:\n*zero.*\n\n###### Watch for:\n*Any sustained non-zero value (protocol version mismatch, corrupted messages, or an unclassified new message type).*\n\n###### Keywords:\n- **Overlay** *(per node)* \u2014 the peer-to-peer network layer over which nodes exchange transactions, proposals, and validations.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`\n\n###### References:\n[Overlay](https://xrpl.org/docs/concepts/networks-and-servers/peer-protocol) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#overlay)", "type": "timeseries", "gridPos": { "h": 8, @@ -360,32 +414,36 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(unknown_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Unknown Bytes In\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(unknown_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Unknown Bytes In\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(unknown_bytes_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Unknown Bytes Out\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(unknown_bytes_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Unknown Bytes Out\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(unknown_messages_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Unknown Messages In\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(unknown_messages_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Unknown Messages In\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(unknown_messages_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Unknown Messages Out\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(unknown_messages_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Unknown Messages Out\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "short", "custom": { "axisLabel": "Count", @@ -417,7 +475,7 @@ }, { "title": "Proof Path Traffic", - "description": "###### What this is:\n*Proof-path request/response bytes used to verify individual ledger entries without downloading the whole ledger.*\n\n###### How it's computed:\n*Per-second in/out byte rate for the proof-path request and response categories.*\n\n###### Reading it:\n*Rises when peers verify specific state, often during catch-up.*\n\n###### Healthy range:\n*workload-dependent.*\n\n###### Watch for:\n*High sustained request volume (heavy state-verification load).*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`", + "description": "###### What this is:\n*Proof-path request/response bytes used to verify individual ledger entries without downloading the whole ledger.*\n\n###### How it's computed:\n*Per-second in/out byte rate for the proof-path request and response categories.*\n\n###### Reading it:\n*Rises when peers verify specific state, often during catch-up.*\n\n###### Healthy range:\n*workload-dependent.*\n\n###### Watch for:\n*High sustained request volume (heavy state-verification load).*\n\n###### Keywords:\n- **Path request / discovery** *(per node)* \u2014 a client's ongoing pathfinding subscription (request) and the periodic path-refresh passes (discovery).\n- **Proof path** *(per node)* \u2014 messages that prove a single ledger entry exists without transferring the whole ledger.\n- **Back-fill / catch-up** *(per node)* \u2014 fetching missing historical ledgers from peers to fill gaps or reach the network tip.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`\n\n###### References:\n[Path request / discovery](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods/path-and-order-book-methods/path_find) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#path-request-discovery)", "type": "timeseries", "gridPos": { "h": 8, @@ -435,32 +493,36 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(proof_path_request_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Request Bytes In\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(proof_path_request_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Request Bytes In\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(proof_path_request_bytes_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Request Bytes Out\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(proof_path_request_bytes_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Request Bytes Out\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(proof_path_response_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Response Bytes In\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(proof_path_response_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Response Bytes In\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(proof_path_response_bytes_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Response Bytes Out\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(proof_path_response_bytes_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Response Bytes Out\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "Bps", "custom": { "axisLabel": "Bytes", @@ -475,7 +537,7 @@ }, { "title": "Replay Delta Traffic", - "description": "###### What this is:\n*Replay-delta request/response bytes used to efficiently replay ledger state changes during catch-up.*\n\n###### How it's computed:\n*Per-second in/out byte rate for the replay-delta request and response categories.*\n\n###### Reading it:\n*Active during catch-up and replay; quiet when synced.*\n\n###### Healthy range:\n*workload-dependent; low when synced.*\n\n###### Watch for:\n*Continuous replay traffic (node repeatedly replaying rather than staying current).*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`", + "description": "###### What this is:\n*Replay-delta request/response bytes used to efficiently replay ledger state changes during catch-up.*\n\n###### How it's computed:\n*Per-second in/out byte rate for the replay-delta request and response categories.*\n\n###### Reading it:\n*Active during catch-up and replay; quiet when synced.*\n\n###### Healthy range:\n*workload-dependent; low when synced.*\n\n###### Watch for:\n*Continuous replay traffic (node repeatedly replaying rather than staying current).*\n\n###### Keywords:\n- **Replay delta** *(per node)* \u2014 messages carrying just the changes between ledgers, to replay state efficiently during catch-up.\n- **Back-fill / catch-up** *(per node)* \u2014 fetching missing historical ledgers from peers to fill gaps or reach the network tip.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[OverlayImpl.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/OverlayImpl.cpp)\n\n###### Function:\n`OverlayImpl ctor (TrafficGauges)`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#replay-delta)", "type": "timeseries", "gridPos": { "h": 8, @@ -493,32 +555,36 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(replay_delta_request_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Request Bytes In\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(replay_delta_request_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Request Bytes In\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(replay_delta_request_bytes_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Request Bytes Out\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(replay_delta_request_bytes_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Request Bytes Out\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(replay_delta_response_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Response Bytes In\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(replay_delta_response_bytes_in{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Response Bytes In\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(replay_delta_response_bytes_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Response Bytes Out\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(replay_delta_response_bytes_out{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Response Bytes Out\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "Bps", "custom": { "axisLabel": "Bytes", @@ -545,7 +611,7 @@ }, { "title": "GetObject Handler Latency Breakdown", - "description": "###### What this is:\n*The three additive parts of TMGetObjectByHash service time, drawn on one axis so the expensive part names itself. Queue Wait is how long the RcvGetObjByHash job sat queued before a worker took it; its job type, ledgerRequest, allows only 3 to run at once. Handler Total is the whole job body once running. NodeStore Lookup is only the fetch loop inside that body. End-to-end service time is Queue Wait plus Handler Total, and Handler Total itself splits into NodeStore Lookup plus everything else.*\n\n###### How it's computed:\n*p99 of job_queued_us and job_running_us, both filtered to handler=\"RcvGetObjByHash\", plus p99 of getobject_lookup_us. Each is histogram_quantile over the microsecond bucket series, summed by le so the quantile is computed across the whole bucket set. The handler label is the sanitised addJob name, so RcvGetObjByHash is separated from RcvGetLedger even though both are job type ledgerRequest.*\n\n###### Reading it:\n*Read it as a subtraction, not as three independent lines. The vertical gap between Handler Total and NodeStore Lookup is the serialization and protobuf reply-building cost, because those are the only other things the job body does. So: Queue Wait tall with Handler Total flat means queue contention and the work itself is fine. Handler Total tracking NodeStore Lookup closely means storage is the bottleneck. Handler Total well above NodeStore Lookup means the cost has moved out of the fetch loop into reply construction. All three flat means this path is not the source of the slowness.*\n\n###### Healthy range:\n*All three sub-millisecond while peers ask for the usual 8 hashes per request; workload-dependent above that.*\n\n###### Watch for:\n*Queue Wait climbing while the other two stay flat: the ledgerRequest queue is saturated, so cross-check Job Queue Backlog and Deferred by Type and LedgerReq Wait by Handler on the Ledger Data and Sync dashboard to see which producer is starving it. A widening Handler Total minus NodeStore Lookup gap: reply construction regressed. NodeStore Lookup rising on its own: check getobject_lookups_total misses and the NuDB panels.*\n\n###### Source:\n[PeerImp.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/PeerImp.cpp)\n\n###### Function:\n`PeerImp::recordGetObjectMetrics (lookup) / MetricsRegistry::recordJobStarted, recordJobFinished (queue, total)`", + "description": "###### What this is:\n*The three additive parts of TMGetObjectByHash service time, drawn on one axis so the expensive part names itself. Queue Wait is how long the RcvGetObjByHash job sat queued before a worker took it; its job type, ledgerRequest, allows only 3 to run at once. Handler Total is the whole job body once running. NodeStore Lookup is only the fetch loop inside that body. End-to-end service time is Queue Wait plus Handler Total, and Handler Total itself splits into NodeStore Lookup plus everything else.*\n\n###### How it's computed:\n*p99 of job_queued_us and job_running_us, both filtered to handler=\"RcvGetObjByHash\", plus p99 of getobject_lookup_us. Each is histogram_quantile over the microsecond bucket series, summed by le so the quantile is computed across the whole bucket set. The handler label is the sanitized addJob name, so RcvGetObjByHash is separated from RcvGetLedger even though both are job type ledgerRequest.*\n\n###### Reading it:\n*Read it as a subtraction, not as three independent lines. The timed fetch loop covers both the NodeStore fetches and the copying of each returned object into the reply, so the vertical gap between Handler Total and NodeStore Lookup is what happens after the loop: serializing the reply message, plus computing the charge and recording the metrics. So: Queue Wait tall with Handler Total flat means queue contention and the work itself is fine. Handler Total tracking NodeStore Lookup closely means storage is the bottleneck. Handler Total well above NodeStore Lookup means the cost has moved out of the fetch loop into reply serialization.*\n\n###### Healthy range:\n*All three sub-millisecond while peers ask for the handful of objects the sync path produces; workload-dependent above that.*\n\n###### Watch for:\n*Queue Wait climbing while the other two stay flat: the ledgerRequest queue is saturated, so cross-check Job Queue Backlog and Deferred by Type and LedgerReq Wait by Handler on the Ledger Data and Sync dashboard to see which producer is starving it. A widening Handler Total minus NodeStore Lookup gap: reply serialization regressed. NodeStore Lookup rising on its own: check getobject_lookups_total misses and the NuDB panels.*\n\n###### Keywords:\n- **GetObject / object fetch** *(per node)* \u2014 peer requests that fetch individual pieces of ledger data by hash, such as tree nodes or transactions.\n- **Handler label** *(per node)* \u2014 the addJob call-site name attached to job metrics, so producers sharing one job type stay separable.\n- **NodeStore lookup (hit / miss)** *(per node)* \u2014 one object-store fetch by hash; a hit is usually served from cache, a miss does a disk seek.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[PeerImp.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/PeerImp.cpp)\n\n###### Function:\n`PeerImp::recordGetObjectMetrics (lookup) / MetricsRegistry::recordJobStarted, recordJobFinished (queue, total)`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#getobject-object-fetch)", "type": "timeseries", "gridPos": { "h": 8, @@ -558,11 +624,6 @@ "maxHeight": 600, "mode": "multi", "sort": "desc" - }, - "legend": { - "displayMode": "table", - "placement": "right", - "calcs": ["mean", "max"] } }, "targets": [ @@ -570,27 +631,27 @@ "datasource": { "type": "prometheus" }, - "expr": "label_replace(histogram_quantile(0.99, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_queued_us_bucket{handler=\"RcvGetObjByHash\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]))), \"series\", \"Queue Wait p99\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(histogram_quantile(0.99, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_queued_us_bucket{handler=\"RcvGetObjByHash\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]))), \"series\", \"Queue Wait p99\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { "type": "prometheus" }, - "expr": "label_replace(histogram_quantile(0.99, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_running_us_bucket{handler=\"RcvGetObjByHash\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]))), \"series\", \"Handler Total p99\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(histogram_quantile(0.99, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(job_running_us_bucket{handler=\"RcvGetObjByHash\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]))), \"series\", \"Handler Total p99\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { "type": "prometheus" }, - "expr": "label_replace(histogram_quantile(0.99, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(getobject_lookup_us_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]))), \"series\", \"NodeStore Lookup p99\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(histogram_quantile(0.99, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(getobject_lookup_us_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]))), \"series\", \"NodeStore Lookup p99\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "µs", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "unit": "\u00b5s", "custom": { - "axisLabel": "Duration (µs)", + "axisLabel": "Duration (\u00b5s)", "spanNulls": 1800000, "insertNulls": false, "showPoints": "auto", @@ -608,7 +669,7 @@ }, { "title": "GetObject Request Size Distribution", - "description": "###### What this is:\n*How many objects peers ask for per TMGetObjectByHash message, as a full distribution rather than an average. This characterises the request that caused any latency seen in the breakdown panel: large batches make the work genuinely large, which is a different problem from the same work becoming slower.*\n\n###### How it's computed:\n*Counts of requests falling in each object-count band per 5-minute window, from the getobject_request_objects bucket series, drawn as colour density.*\n\n###### Reading it:\n*A tight band at the bottom is honest traffic: InboundLedger asks for at most 8 hashes per call. Bands above 64 and above 1024 are the medium and large pricing bands, so mass there means the size surcharge is being applied. A hot cell in the top row is the overflow bucket and means requests larger than the top bucket boundary.*\n\n###### Healthy range:\n*Nearly all mass in the lowest bands (8 objects or fewer per request).*\n\n###### Watch for:\n*Mass appearing in the high bands, especially a persistent hot row near the top: a peer is batching thousands of hashes per message, which is what the differential pricing exists to charge for. Confirm with GetObject Charge Distribution and GetObject Rejections. Buckets are explicit (1,2,4,8,16,64,256,1024,4096,12288) and reach the handler's hard cap, so the top row is real traffic at the cap, not a measurement ceiling.*\n\n###### Source:\n[PeerImp.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/PeerImp.cpp)\n\n###### Function:\n`PeerImp::recordGetObjectMetrics`", + "description": "###### What this is:\n*How many objects peers ask for per TMGetObjectByHash message, as a full distribution rather than an average. This characterizes the request that caused any latency seen in the breakdown panel: large batches make the work genuinely large, which is a different problem from the same work becoming slower.*\n\n###### How it's computed:\n*Counts of requests falling in each object-count band per 5-minute window, from the getobject_request_objects bucket series, drawn as color density.*\n\n###### Reading it:\n*A tight band at the bottom is honest traffic: the inbound-ledger acquire path asks for at most 4 hashes of one object type per message. Bands above 64 and above 1024 are the medium and large pricing bands, so mass there means the size surcharge is being applied. A hot cell in the top row is the overflow bucket and means requests larger than the top bucket boundary.*\n\n###### Healthy range:\n*Nearly all mass in the lowest bands (8 objects or fewer per request).*\n\n###### Watch for:\n*Mass appearing in the high bands, especially a persistent hot row near the top: a peer is batching thousands of hashes per message, which is what the differential pricing exists to charge for. Confirm with GetObject Charge Distribution and GetObject Rejections. Buckets are explicit (1,2,4,8,16,64,256,1024,4096,12288) and reach the handler's hard cap, so the top row is real traffic at the cap, not a measurement ceiling.*\n\n###### Keywords:\n- **GetObject / object fetch** *(per node)* \u2014 peer requests that fetch individual pieces of ledger data by hash, such as tree nodes or transactions.\n- **Resource charge** *(per node)* \u2014 the load cost the resource manager bills a peer per request; crossing the warning then drop threshold sheds the peer.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[PeerImp.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/PeerImp.cpp)\n\n###### Function:\n`PeerImp::recordGetObjectMetrics`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#getobject-object-fetch)", "type": "heatmap", "gridPos": { "h": 8, @@ -639,18 +700,14 @@ ], "fieldConfig": { "defaults": { - "unit": "short", - "custom": { - "spanNulls": 1800000, - "insertNulls": false - } + "unit": "short" }, "overrides": [] } }, { "title": "GetObject Lookups by Result", - "description": "###### What this is:\n*NodeStore lookups performed by the handler, split into hits and misses. A miss does a node-store seek while a hit is usually served from cache, so the hit/miss mix is the reason NodeStore Lookup time moves.*\n\n###### How it's computed:\n*Per-second rate of getobject_lookups_total, grouped by the result label. The counter is advanced once per request with the batch total, not once per object, so the rate is objects per second rather than requests per second.*\n\n###### Reading it:\n*Use this to explain the NodeStore Lookup line on the breakdown panel. A miss-heavy mix makes that line rise for a real reason: seeks, not a regression. A hit-heavy mix with rising lookup time points at the storage layer instead.*\n\n###### Healthy range:\n*Hits dominating on a warm synced node; misses low and driven by genuine catch-up requests.*\n\n###### Watch for:\n*A sustained miss rate far above the hit rate: a peer is asking for hashes this node does not hold, which is either a badly out-of-sync peer or probing traffic. Cross-check GetObject Charge Distribution, since misses are billed first and at eight times the hit cost.*\n\n###### Source:\n[PeerImp.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/PeerImp.cpp)\n\n###### Function:\n`PeerImp::recordGetObjectMetrics`", + "description": "###### What this is:\n*NodeStore lookups performed by the handler, split into hits and misses. A miss does a node-store seek while a hit is usually served from cache, so the hit/miss mix is the reason NodeStore Lookup time moves.*\n\n###### How it's computed:\n*Per-second rate of getobject_lookups_total, grouped by the result label. The counter is advanced once per request with the batch totals -- hits are the objects returned, misses are the rest of the request -- not once per object, so the rate is objects per second rather than requests per second.*\n\n###### Reading it:\n*Use this to explain the NodeStore Lookup line on the breakdown panel. A miss-heavy mix makes that line rise for a real reason: seeks, not a regression. A hit-heavy mix with rising lookup time points at the storage layer instead.*\n\n###### Healthy range:\n*Hits dominating on a warm synced node; misses low and driven by genuine catch-up requests.*\n\n###### Watch for:\n*A sustained miss rate far above the hit rate: a peer is asking for hashes this node does not hold, which is either a peer far out of sync or a client requesting objects this node never stored. Cross-check GetObject Charge Distribution, since misses are billed first and at eight times the hit cost.*\n\n###### Keywords:\n- **NodeStore lookup (hit / miss)** *(per node)* \u2014 one object-store fetch by hash; a hit is usually served from cache, a miss does a disk seek.\n- **NodeStore** *(per node)* \u2014 the key-value object store holding ledger data (tree nodes), backed by NuDB.\n- **Resource charge** *(per node)* \u2014 the load cost the resource manager bills a peer per request; crossing the warning then drop threshold sheds the peer.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[PeerImp.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/PeerImp.cpp)\n\n###### Function:\n`PeerImp::recordGetObjectMetrics`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#nodestore-lookup-hit-miss)", "type": "timeseries", "gridPos": { "h": 8, @@ -670,12 +727,12 @@ "datasource": { "type": "prometheus" }, - "expr": "label_replace(sum by (result, service_instance_id, xrpl_branch, xrpl_node_role) (rate(getobject_lookups_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", result=~\"$result\"}[$__rate_interval])), \"series\", \"$1\", \"result\", \"(.*)\")" + "expr": "label_replace(label_join(label_replace(sum by (result, service_instance_id, xrpl_branch, xrpl_node_role) (rate(getobject_lookups_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", result=~\"$result\"}[$__rate_interval])), \"series\", \"$1\", \"result\", \"(.*)\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: lookups/s", "custom": { "axisLabel": "Lookups / Sec", @@ -690,7 +747,7 @@ }, { "title": "GetObject Rejections", - "description": "###### What this is:\n*Requests refused by the message handler before any NodeStore access, split by which gate refused them: oversize means more objects than the handler accepts, malformed_ledgerhash means the ledger hash was not 32 bytes.*\n\n###### How it's computed:\n*Per-second rate of getobject_rejected_total, grouped by the reason label. Both gates run in onMessage before the job is queued, so a rejection consumes no queue slot and no NodeStore lookup.*\n\n###### Reading it:\n*Any non-zero value is traffic that does not conform to the protocol: an honest peer asks for at most 8 hashes and always sends a full-size hash. Because the gates fire before the fetch loop, rejections explain why request volume can be high while lookups stay flat.*\n\n###### Healthy range:\n*Zero. No honest peer produces either rejection.*\n\n###### Watch for:\n*A rising oversize rate: a peer is probing the request-size limit. Confirm the pricing response on GetObject Charge Distribution, and expect the peer to be disconnected once its resource balance crosses the drop threshold. A rising malformed rate points at a broken or hostile client rather than at load.*\n\n###### Source:\n[PeerImp.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/PeerImp.cpp)\n\n###### Function:\n`PeerImp::onMessage (TMGetObjectByHash)`", + "description": "###### What this is:\n*Requests refused by the message handler before any NodeStore access, split by which gate refused them: oversize means more objects than the handler accepts (its hard cap is 12288), malformed_ledgerhash means the ledger hash was not 32 bytes.*\n\n###### How it's computed:\n*Per-second rate of getobject_rejected_total, grouped by the reason label. Both gates run in onMessage on the generic query path before the job is queued, so a rejection consumes no queue slot and no NodeStore lookup. The fetch-pack and transaction sub-types return earlier and never reach either gate.*\n\n###### Reading it:\n*Any non-zero value is traffic that does not conform to the protocol: the sync path asks for a handful of hashes and always sends a full-size hash. Because the gates fire before the fetch loop, rejections explain why request volume can be high while lookups stay flat.*\n\n###### Healthy range:\n*Zero. No conforming peer produces either rejection, so a flat zero line is the expected reading and is not on its own evidence that the counter is wired -- confirm that from the other GetObject panels, which do move on a healthy node.*\n\n###### Watch for:\n*A rising oversize rate: a peer is sending requests above the accepted object count. Confirm the pricing response on GetObject Charge Distribution, and expect the peer to be shed once its resource balance crosses the drop threshold. A rising malformed rate points at a broken or non-conforming client rather than at load.*\n\n###### Keywords:\n- **GetObject / object fetch** *(per node)* \u2014 peer requests that fetch individual pieces of ledger data by hash, such as tree nodes or transactions.\n- **Resource drops / warnings** *(per node)* \u2014 the resource manager warning (then dropping/blocking) a peer or client for excessive usage.\n- **Resource charge** *(per node)* \u2014 the load cost the resource manager bills a peer per request; crossing the warning then drop threshold sheds the peer.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[PeerImp.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/PeerImp.cpp)\n\n###### Function:\n`PeerImp::onMessage (TMGetObjectByHash)`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#getobject-object-fetch)", "type": "timeseries", "gridPos": { "h": 8, @@ -710,12 +767,12 @@ "datasource": { "type": "prometheus" }, - "expr": "label_replace(sum by (reason, service_instance_id, xrpl_branch, xrpl_node_role) (rate(getobject_rejected_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", reason=~\"$reason\"}[$__rate_interval])), \"series\", \"$1\", \"reason\", \"(.*)\")" + "expr": "label_replace(label_join(label_replace(sum by (reason, service_instance_id, xrpl_branch, xrpl_node_role) (rate(getobject_rejected_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", reason=~\"$reason\"}[$__rate_interval])), \"series\", \"$1\", \"reason\", \"(.*)\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: rejections/s", "custom": { "axisLabel": "Rejections / Sec", @@ -730,7 +787,7 @@ }, { "title": "GetObject Charge Distribution", - "description": "###### What this is:\n*The dynamic resource charge applied per TMGetObjectByHash request, as percentiles. This is the differential-pricing component only, so it shows whether cost actually escalates with request size and miss ratio the way the pricing model intends.*\n\n###### How it's computed:\n*p50, p90 and p99 of getobject_charge over the dashboard rate interval, from its bucket series summed by le. The value recorded is the charge that was applied, computed from billable hits, billable misses and the request-size band.*\n\n###### Reading it:\n*p50 sitting at zero is the healthy shape: requests inside the free allowance cost nothing. Movement in p99 while p50 stays at zero means a small number of expensive requests, which is exactly the traffic the model is meant to price. Compare with GetObject Request Size Distribution: charge should rise in steps as requests cross the size-band edges, not smoothly.*\n\n###### Healthy range:\n*p50 at zero, p99 low. Honest requests of 16 objects or fewer are free by design.*\n\n###### Watch for:\n*p99 climbing steadily: sustained expensive traffic, and the peers producing it should be approaching the resource drop threshold. Buckets are explicit and bracket the resource thresholds (5000 warning, 25000 drop), so p99 crossing 25000 means senders are being disconnected on a single message. The axis is deliberately unscaled rather than abbreviated, so those two numbers are readable exactly rather than as 5 K and 25 K.*\n\n###### Source:\n[PeerImp.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/PeerImp.cpp)\n\n###### Function:\n`PeerImp::computeGetObjectByHashFee`", + "description": "###### What this is:\n*The dynamic resource charge applied per TMGetObjectByHash request, as percentiles. This is the differential-pricing component only -- a flat base charge is applied separately when the message is admitted -- so it shows whether cost actually escalates with request size and miss ratio the way the pricing model intends.*\n\n###### How it's computed:\n*p50, p90 and p99 of getobject_charge over the dashboard rate interval, from its bucket series summed by le. The value recorded is the charge that was applied, computed from billable hits, billable misses and the request-size band.*\n\n###### Reading it:\n*p50 sitting at zero is the healthy shape: requests inside the free allowance cost nothing. Movement in p99 while p50 stays at zero means a small number of expensive requests, which is exactly the traffic the model is meant to price. Compare with GetObject Request Size Distribution: charge should rise in steps as requests cross the size-band edges at 64 and 1024, not smoothly.*\n\n###### Healthy range:\n*p50 at zero, p99 low. Requests of 16 objects or fewer carry no dynamic charge by design.*\n\n###### Watch for:\n*p99 climbing steadily: sustained expensive traffic, and the peers producing it should be approaching the resource drop threshold. Buckets are explicit and bracket the resource thresholds (5000 warning, 25000 drop), so p99 crossing 25000 means senders are being shed on a single message. The axis is deliberately unscaled rather than abbreviated, so those two numbers are readable exactly rather than as 5 K and 25 K.*\n\n###### Keywords:\n- **Resource charge** *(per node)* \u2014 the load cost the resource manager bills a peer per request; crossing the warning then drop threshold sheds the peer.\n- **Resource drops / warnings** *(per node)* \u2014 the resource manager warning (then dropping/blocking) a peer or client for excessive usage.\n- **GetObject / object fetch** *(per node)* \u2014 peer requests that fetch individual pieces of ledger data by hash, such as tree nodes or transactions.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[PeerImp.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/PeerImp.cpp)\n\n###### Function:\n`PeerImp::computeGetObjectByHashFee (charge) / PeerImp::recordGetObjectMetrics (recording)`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#resource-charge)", "type": "timeseries", "gridPos": { "h": 8, @@ -750,24 +807,24 @@ "datasource": { "type": "prometheus" }, - "expr": "label_replace(histogram_quantile(0.5, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(getobject_charge_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]))), \"series\", \"Charge p50\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(histogram_quantile(0.5, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(getobject_charge_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]))), \"series\", \"Charge p50\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { "type": "prometheus" }, - "expr": "label_replace(histogram_quantile(0.9, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(getobject_charge_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]))), \"series\", \"Charge p90\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(histogram_quantile(0.9, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(getobject_charge_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]))), \"series\", \"Charge p90\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { "type": "prometheus" }, - "expr": "label_replace(histogram_quantile(0.99, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(getobject_charge_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]))), \"series\", \"Charge p99\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(histogram_quantile(0.99, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(getobject_charge_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]))), \"series\", \"Charge p99\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "none", "custom": { "axisLabel": "Charge (Cost Units)", @@ -791,6 +848,19 @@ "tags": ["network", "peer"], "templating": { "list": [ + { + "name": "DS_PROMETHEUS", + "type": "datasource", + "label": "Prometheus", + "query": "prometheus", + "regex": "", + "current": {}, + "hide": 0, + "refresh": 1, + "includeAll": false, + "multi": false, + "options": [] + }, { "name": "service_name", "label": "Service Name", @@ -799,7 +869,7 @@ "query": "label_values(service_name)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -819,7 +889,7 @@ "query": "label_values(deployment_environment)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -839,7 +909,7 @@ "query": "label_values(xrpl_network_type)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -919,7 +989,7 @@ "query": "label_values(target_info, service_instance_id)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -978,5 +1048,6 @@ "to": "now" }, "title": "Overlay Traffic Detail", - "uid": "overlay-traffic-detail" + "uid": "overlay-traffic-detail", + "refresh": "30s" } diff --git a/docker/telemetry/grafana/dashboards/peer-network.json b/docker/telemetry/grafana/dashboards/peer-network.json index 47f8cf1a04..44fde48888 100644 --- a/docker/telemetry/grafana/dashboards/peer-network.json +++ b/docker/telemetry/grafana/dashboards/peer-network.json @@ -1,8 +1,38 @@ { "annotations": { - "list": [] + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + }, + { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": false, + "iconColor": "rgb(70, 70, 70)", + "name": "Annotate perf-iac runs", + "target": { + "limit": 100, + "matchAny": false, + "tags": ["perf-iac"], + "type": "tags" + }, + "type": "tags" + } + ] }, - "description": "What this shows: Consensus proposals and validations received from peers, and transaction relay efficiency for this node.\nUse it to: Check that peers are feeding this node proposals, validations, and transactions, and gauge relay overhead.", + "description": "What this shows: Consensus proposals and validations received from peers, and transaction relay efficiency for this node. \u2014 Use it to: Check that peers are feeding this node proposals, validations, and transactions, and gauge relay overhead.", "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 1, @@ -11,7 +41,7 @@ "panels": [ { "title": "Peer Proposal Receive Rate", - "description": "###### What this is:\n*How many consensus proposals this node receives from peers per second.*\n\n###### How it's computed:\n*Per-second rate of received proposals over 5 minutes, per node.*\n\n###### Reading it:\n*A steady rate roughly proportional to the number of proposing validators.*\n\n###### Healthy range:\n*Workload-dependent; scales with validator count and connectivity.*\n\n###### Watch for:\n*A drop toward zero (isolation from the network) or a sudden flood far above baseline (proposal spam).*\n\n###### Source:\n[PeerImp.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/PeerImp.cpp)\n\n###### Function:\n`PeerImp::onMessage(TMProposeSet)`", + "description": "###### What this is:\n*How many consensus proposals this node receives from peers per second.*\n\n###### How it's computed:\n*Per-second rate of received proposals over 5 minutes, per node.*\n\n###### Reading it:\n*A steady rate roughly proportional to the number of proposing validators.*\n\n###### Healthy range:\n*Workload-dependent; scales with validator count and connectivity.*\n\n###### Watch for:\n*A drop toward zero (isolation from the network) or a sudden flood far above baseline (proposal spam).*\n\n###### Keywords:\n- **Consensus** *(network event)* \u2014 the protocol by which validators agree on the next ledger's transaction set and close time.\n- **Proposal** *(network event)* \u2014 a validator's advertised set of candidate transactions for the next ledger, revised each round.\n- **Proposers** *(network event)* \u2014 the count of validators whose proposals this node heard in the last closed round.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[PeerImp.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/PeerImp.cpp)\n\n###### Function:\n`PeerImp::onMessage(TMProposeSet)`\n\n###### References:\n[Consensus](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#consensus)", "type": "timeseries", "gridPos": { "h": 8, @@ -29,14 +59,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"peer.proposal.receive\"}[$__rate_interval])), \"series\", \"Proposals Received / Sec\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"peer.proposal.receive\"}[$__rate_interval])), \"series\", \"Proposals Received / Sec\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: proposals/s", "custom": { "axisLabel": "Proposals / Sec", @@ -51,7 +82,7 @@ }, { "title": "Peer Validation Receive Rate", - "description": "###### What this is:\n*How many ledger validations this node receives from peers per second.*\n\n###### How it's computed:\n*Per-second rate of received validations over 5 minutes, per node.*\n\n###### Reading it:\n*Steady and proportional to the number of validators the node hears from.*\n\n###### Healthy range:\n*Workload-dependent; roughly one burst per validator per closed ledger.*\n\n###### Watch for:\n*A fall toward zero (loss of validator connectivity) or an abnormal surge from untrusted sources.*\n\n###### Source:\n[PeerImp.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/PeerImp.cpp)\n\n###### Function:\n`PeerImp::onMessage(TMValidation)`", + "description": "###### What this is:\n*How many ledger validations this node receives from peers per second.*\n\n###### How it's computed:\n*Per-second rate of received validations over 5 minutes, per node.*\n\n###### Reading it:\n*Steady and proportional to the number of validators the node hears from.*\n\n###### Healthy range:\n*Workload-dependent; roughly one burst per validator per closed ledger.*\n\n###### Watch for:\n*A fall toward zero (loss of validator connectivity) or an abnormal surge from untrusted sources.*\n\n###### Keywords:\n- **Trusted / untrusted / duplicate** *(per node)* \u2014 message classification: trusted (from UNL validators), untrusted (others), or duplicate (already seen).\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[PeerImp.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/PeerImp.cpp)\n\n###### Function:\n`PeerImp::onMessage(TMValidation)`\n\n###### References:\n[Trusted / untrusted / duplicate](https://xrpl.org/docs/concepts/consensus-protocol/unl) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#trusted-untrusted-duplicate)", "type": "timeseries", "gridPos": { "h": 8, @@ -69,14 +100,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"peer.validation.receive\"}[$__rate_interval])), \"series\", \"Validations Received / Sec\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"peer.validation.receive\"}[$__rate_interval])), \"series\", \"Validations Received / Sec\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: validations/s", "custom": { "axisLabel": "Validations / Sec", @@ -91,7 +123,7 @@ }, { "title": "Proposals Trusted vs Untrusted", - "description": "###### What this is:\n*The share of received proposals that come from trusted (UNL) validators versus everyone else.*\n\n###### How it's computed:\n*Received-proposal rate split by trust status over 5 minutes, shown as proportions.*\n\n###### Reading it:\n*A healthy node with a good UNL shows a solid trusted slice.*\n\n###### Healthy range:\n*Workload-dependent; a meaningful trusted fraction is expected.*\n\n###### Watch for:\n*A collapsing trusted share or a large untrusted volume, consistent with proposal flooding from non-UNL peers.*\n\n###### Source:\n[PeerImp.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/PeerImp.cpp)\n\n###### Function:\n`PeerImp::onMessage(TMProposeSet)`", + "description": "###### What this is:\n*The share of received proposals that come from trusted (UNL) validators versus everyone else.*\n\n###### How it's computed:\n*Received-proposal rate split by trust status over 5 minutes, shown as proportions.*\n\n###### Reading it:\n*A healthy node with a good UNL shows a solid trusted slice.*\n\n###### Healthy range:\n*Workload-dependent; a meaningful trusted fraction is expected.*\n\n###### Watch for:\n*A collapsing trusted share or a large untrusted volume, consistent with proposal flooding from non-UNL peers.*\n\n###### Keywords:\n- **Proposal** *(network event)* \u2014 a validator's advertised set of candidate transactions for the next ledger, revised each round.\n- **Trusted / untrusted / duplicate** *(per node)* \u2014 message classification: trusted (from UNL validators), untrusted (others), or duplicate (already seen).\n- **UNL (Unique Node List)** *(per node)* \u2014 the list of validators a node trusts not to collude; the basis for its consensus and quorum.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[PeerImp.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/PeerImp.cpp)\n\n###### Function:\n`PeerImp::onMessage(TMProposeSet)`\n\n###### References:\n[Proposal](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) \u00b7 [Trusted / untrusted / duplicate](https://xrpl.org/docs/concepts/consensus-protocol/unl) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#proposal)", "type": "piechart", "gridPos": { "h": 8, @@ -109,14 +141,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (proposal_trusted, service_instance_id, xrpl_branch, xrpl_node_role) (increase(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", proposal_trusted=~\"$proposal_trusted\", span_name=\"peer.proposal.receive\"}[$__rate_interval])), \"series\", \"Trusted = $1\", \"proposal_trusted\", \"(.*)\")" + "expr": "label_replace(label_join(label_replace(sum by (proposal_trusted, service_instance_id, xrpl_branch, xrpl_node_role) (increase(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", proposal_trusted=~\"$proposal_trusted\", span_name=\"peer.proposal.receive\"}[$__rate_interval])), \"series\", \"Trusted = $1\", \"proposal_trusted\", \"(.*)\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "short" }, "overrides": [] @@ -124,7 +157,7 @@ }, { "title": "Validations Trusted vs Untrusted", - "description": "###### What this is:\n*The share of received validations from trusted (UNL) validators versus untrusted sources.*\n\n###### How it's computed:\n*Received-validation rate split by trust status over 5 minutes, shown as proportions.*\n\n###### Reading it:\n*The trusted slice should dominate for a well-configured node.*\n\n###### Healthy range:\n*Workload-dependent; trusted validations expected to be the majority.*\n\n###### Watch for:\n*A shrinking trusted share or a spike of untrusted validations, which can indicate misconfiguration or abuse.*\n\n###### Source:\n[PeerImp.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/PeerImp.cpp)\n\n###### Function:\n`PeerImp::onMessage(TMValidation)`", + "description": "###### What this is:\n*The share of received validations from trusted (UNL) validators versus untrusted sources.*\n\n###### How it's computed:\n*Received-validation rate split by trust status over 5 minutes, shown as proportions.*\n\n###### Reading it:\n*The trusted slice should dominate for a well-configured node.*\n\n###### Healthy range:\n*Workload-dependent; trusted validations expected to be the majority.*\n\n###### Watch for:\n*A shrinking trusted share or a spike of untrusted validations, which can indicate misconfiguration or abuse.*\n\n###### Keywords:\n- **Trusted / untrusted / duplicate** *(per node)* \u2014 message classification: trusted (from UNL validators), untrusted (others), or duplicate (already seen).\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[PeerImp.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/PeerImp.cpp)\n\n###### Function:\n`PeerImp::onMessage(TMValidation)`\n\n###### References:\n[Trusted / untrusted / duplicate](https://xrpl.org/docs/concepts/consensus-protocol/unl) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#trusted-untrusted-duplicate)", "type": "piechart", "gridPos": { "h": 8, @@ -142,14 +175,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (validation_trusted, service_instance_id, xrpl_branch, xrpl_node_role) (increase(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", validation_trusted=~\"$validation_trusted\", span_name=\"peer.validation.receive\"}[$__rate_interval])), \"series\", \"Trusted = $1\", \"validation_trusted\", \"(.*)\")" + "expr": "label_replace(label_join(label_replace(sum by (validation_trusted, service_instance_id, xrpl_branch, xrpl_node_role) (increase(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", validation_trusted=~\"$validation_trusted\", span_name=\"peer.validation.receive\"}[$__rate_interval])), \"series\", \"Trusted = $1\", \"validation_trusted\", \"(.*)\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "short" }, "overrides": [] @@ -157,7 +191,7 @@ }, { "title": "Reduce-Relay Peer Selection", - "description": "###### What this is:\n*How transaction relay picks peers: chosen relay sources, suppressed peers, and peers with the feature off.*\n\n###### How it's computed:\n*Current peer counts in each category (selected, suppressed, not-enabled), per node.*\n\n###### Reading it:\n*A high suppressed-to-selected ratio means relay is saving bandwidth as intended.*\n\n###### Healthy range:\n*Workload-dependent; suppressed should exceed selected in a well-connected mesh.*\n\n###### Watch for:\n*A large not-enabled count (older peers forcing full relay) or selected climbing while suppressed falls.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerReduceRelayGauge`", + "description": "###### What this is:\n*How transaction relay picks peers: chosen relay sources, suppressed peers, and peers with the feature off.*\n\n###### How it's computed:\n*Current peer counts in each category (selected, suppressed, not-enabled), per node.*\n\n###### Reading it:\n*A high suppressed-to-selected ratio means relay is saving bandwidth as intended.*\n\n###### Healthy range:\n*Workload-dependent; suppressed should exceed selected in a well-connected mesh.*\n\n###### Watch for:\n*A large not-enabled count (older peers forcing full relay) or selected climbing while suppressed falls.*\n\n###### Keywords:\n- **Reduce-relay** *(per node)* \u2014 an optimization that relays messages through selected peers only, suppressing redundant forwarding.\n- **Transaction suppression** *(per node)* \u2014 dropping a transaction already seen from another peer, so it is not reprocessed.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerReduceRelayGauge`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#reduce-relay)", "type": "timeseries", "gridPos": { "h": 8, @@ -175,26 +209,29 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(reduce_relay_metrics{metric=\"selected_peers\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Selected\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(reduce_relay_metrics{metric=\"selected_peers\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Selected\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(reduce_relay_metrics{metric=\"suppressed_peers\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Suppressed\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(reduce_relay_metrics{metric=\"suppressed_peers\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Suppressed\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(reduce_relay_metrics{metric=\"not_enabled_peers\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Not Enabled\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(reduce_relay_metrics{metric=\"not_enabled_peers\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Not Enabled\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "short", "custom": { "axisLabel": "Peer Count", @@ -209,7 +246,7 @@ }, { "title": "Reduce-Relay Missing-Tx Frequency", - "description": "###### What this is:\n*How often a peer has to fetch a transaction it missed because relay suppressed it.*\n\n###### How it's computed:\n*The reported frequency of on-demand missing-transaction fetches, per node.*\n\n###### Reading it:\n*Lower is better; near-flat means suppression is well tuned.*\n\n###### Healthy range:\n*Workload-dependent; a low, stable value is expected.*\n\n###### Watch for:\n*A rising trend, meaning suppression is too aggressive and the on-demand fetch path is growing.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerReduceRelayGauge`", + "description": "###### What this is:\n*How often a peer has to fetch a transaction it missed because relay suppressed it.*\n\n###### How it's computed:\n*The reported frequency of on-demand missing-transaction fetches, per node.*\n\n###### Reading it:\n*Lower is better; near-flat means suppression is well tuned.*\n\n###### Healthy range:\n*Workload-dependent; a low, stable value is expected.*\n\n###### Watch for:\n*A rising trend, meaning suppression is too aggressive and the on-demand fetch path is growing.*\n\n###### Keywords:\n- **Reduce-relay** *(per node)* \u2014 an optimization that relays messages through selected peers only, suppressing redundant forwarding.\n- **Transaction suppression** *(per node)* \u2014 dropping a transaction already seen from another peer, so it is not reprocessed.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerReduceRelayGauge`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#reduce-relay)", "type": "timeseries", "gridPos": { "h": 8, @@ -227,14 +264,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(reduce_relay_metrics{metric=\"missing_tx_freq\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Missing Tx Freq\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(reduce_relay_metrics{metric=\"missing_tx_freq\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Missing Tx Freq\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "short", "custom": { "axisLabel": "Frequency", @@ -252,6 +290,19 @@ "tags": ["network", "peer"], "templating": { "list": [ + { + "name": "DS_PROMETHEUS", + "type": "datasource", + "label": "Prometheus", + "query": "prometheus", + "regex": "", + "current": {}, + "hide": 0, + "refresh": 1, + "includeAll": false, + "multi": false, + "options": [] + }, { "name": "service_name", "label": "Service Name", @@ -260,7 +311,7 @@ "query": "label_values(service_name)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -280,7 +331,7 @@ "query": "label_values(deployment_environment)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -300,7 +351,7 @@ "query": "label_values(xrpl_network_type)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -380,7 +431,7 @@ "query": "label_values(target_info, service_instance_id)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -400,7 +451,7 @@ "query": "label_values(span_calls_total{span_name=\"peer.proposal.receive\"}, proposal_trusted)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -420,7 +471,7 @@ "query": "label_values(span_calls_total{span_name=\"peer.validation.receive\"}, validation_trusted)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -440,5 +491,5 @@ }, "title": "Peer Network", "uid": "peer-network", - "refresh": "5s" + "refresh": "30s" } diff --git a/docker/telemetry/grafana/dashboards/peer-quality.json b/docker/telemetry/grafana/dashboards/peer-quality.json index 7d361678e3..e17bb2dda4 100644 --- a/docker/telemetry/grafana/dashboards/peer-quality.json +++ b/docker/telemetry/grafana/dashboards/peer-quality.json @@ -1,8 +1,38 @@ { "annotations": { - "list": [] + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + }, + { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": false, + "iconColor": "rgb(70, 70, 70)", + "name": "Annotate perf-iac runs", + "target": { + "limit": 100, + "matchAny": false, + "tags": ["perf-iac"], + "type": "tags" + }, + "type": "tags" + } + ] }, - "description": "What this shows: Peer connection quality: latency, divergence, version distribution and upgrade guidance, resource disconnects, and inbound/outbound balance.\nUse it to: Assess how good this node's peer set is and whether connectivity or an upgrade needs attention.", + "description": "What this shows: Peer connection quality: latency, divergence, version distribution and upgrade guidance, resource disconnects, and inbound/outbound balance. \u2014 Use it to: Assess how good this node's peer set is and whether connectivity or an upgrade needs attention.", "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 1, @@ -11,11 +41,11 @@ "panels": [ { "title": "P90 Peer Latency", - "description": "###### What this is:\n*90th-percentile round-trip latency to connected peers, in milliseconds.*\n\n###### How it's computed:\n*Instantaneous gauge reading of the p90 peer latency.*\n\n###### Reading it:\n*Lower is better; green under 200ms, yellow to 500ms, red above.*\n\n###### Healthy range:\n*Under 200ms.*\n\n###### Watch for:\n*Rising latency, which points to network congestion or geographically distant, poorly performing peers.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerPeerQualityGauge`", + "description": "###### What this is:\n*90th-percentile round-trip latency to connected peers, in milliseconds.*\n\n###### How it's computed:\n*Instantaneous gauge reading of the p90 peer latency.*\n\n###### Reading it:\n*Lower is better; green under 200ms, yellow to 500ms, red above.*\n\n###### Healthy range:\n*Under 200ms.*\n\n###### Watch for:\n*Rising latency, which points to network congestion or geographically distant, poorly performing peers.*\n\n###### Keywords:\n- **Peer** *(per node)* \u2014 another server this node holds a protocol connection to.\n- **Overlay** *(network-wide)* \u2014 the peer-to-peer mesh xrpld nodes form to gossip transactions, proposals and validations.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerPeerQualityGauge`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#peer)", "type": "timeseries", "gridPos": { "h": 8, - "w": 12, + "w": 24, "x": 0, "y": 0 }, @@ -29,14 +59,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(peer_quality{metric=\"peer_latency_p90_ms\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"P90 Latency\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(peer_quality{metric=\"peer_latency_p90_ms\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"P90 Latency\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "ms", "custom": { "axisLabel": "Latency (ms)", @@ -73,13 +104,13 @@ }, { "title": "Insane/Diverged Peers", - "description": "###### What this is:\n*Count of connected peers whose ledger state has diverged from the network.*\n\n###### How it's computed:\n*Instantaneous gauge reading of the diverged-peer count.*\n\n###### Reading it:\n*Zero is healthy; any count means those peers disagree on ledger state.*\n\n###### Healthy range:\n*0 diverged peers.*\n\n###### Watch for:\n*A persistent non-zero count, which can indicate peers on a fork or misbehaving peers.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerPeerQualityGauge`", + "description": "###### What this is:\n*Count of connected peers whose ledger state has diverged from the network.*\n\n###### How it's computed:\n*Instantaneous gauge reading of the diverged-peer count.*\n\n###### Reading it:\n*Zero is healthy; any count means those peers disagree on ledger state.*\n\n###### Healthy range:\n*0 diverged peers.*\n\n###### Watch for:\n*A persistent non-zero count, which can indicate peers on a fork or misbehaving peers.*\n\n###### Keywords:\n- **Insane / diverged peers** *(per node)* \u2014 connected peers whose ledger state disagrees with the network \u2014 possibly on a fork or misbehaving.\n- **Fork** *(network-wide)* \u2014 when nodes validate divergent ledger chains instead of a single agreed history.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerPeerQualityGauge`\n\n###### References:\n[Fork](https://xrpl.org/docs/concepts/consensus-protocol) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#insane-diverged-peers)", "type": "stat", "gridPos": { "h": 8, - "w": 6, - "x": 12, - "y": 0 + "w": 24, + "x": 0, + "y": 8 }, "options": { "tooltip": { @@ -91,14 +122,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(peer_quality{metric=\"peers_insane_count\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Insane Peers\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(peer_quality{metric=\"peers_insane_count\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Insane Peers\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "short", "thresholds": { "steps": [ @@ -123,13 +155,13 @@ }, { "title": "Higher Version Peers %", - "description": "###### What this is:\n*Percentage of connected peers running a newer rippled version than this node.*\n\n###### How it's computed:\n*Instantaneous gauge reading of the higher-version peer percentage.*\n\n###### Reading it:\n*A high percentage suggests this node is behind and should be upgraded.*\n\n###### Healthy range:\n*Under 30%.*\n\n###### Watch for:\n*A majority of peers on a newer version, a strong upgrade signal.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerPeerQualityGauge`", + "description": "###### What this is:\n*Percentage of connected peers running a newer rippled version than this node.*\n\n###### How it's computed:\n*Instantaneous gauge reading of the higher-version peer percentage.*\n\n###### Reading it:\n*A high percentage suggests this node is behind and should be upgraded.*\n\n###### Healthy range:\n*Under 30%.*\n\n###### Watch for:\n*A majority of peers on a newer version, a strong upgrade signal.*\n\n###### Keywords:\n- **Peer** *(per node)* \u2014 another server this node holds a protocol connection to.\n- **Overlay** *(network-wide)* \u2014 the peer-to-peer mesh xrpld nodes form to gossip transactions, proposals and validations.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerPeerQualityGauge`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#peer)", "type": "stat", "gridPos": { "h": 8, - "w": 6, - "x": 18, - "y": 0 + "w": 24, + "x": 0, + "y": 16 }, "options": { "tooltip": { @@ -141,14 +173,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(peer_quality{metric=\"peers_higher_version_pct\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Higher Version %\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(peer_quality{metric=\"peers_higher_version_pct\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Higher Version %\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "percent", "min": 0, "max": 100, @@ -175,13 +208,13 @@ }, { "title": "Upgrade Recommended", - "description": "###### What this is:\n*A flag indicating whether an upgrade is advised based on peer version analysis (Yes/No).*\n\n###### How it's computed:\n*Instantaneous gauge reading of the upgrade-recommended flag.*\n\n###### Reading it:\n*No is healthy; Yes means most peers run a newer version.*\n\n###### Healthy range:\n*No.*\n\n###### Watch for:\n*A Yes state, indicating the node risks falling out of step with the network.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerPeerQualityGauge`", + "description": "###### What this is:\n*A flag indicating whether an upgrade is advised based on peer version analysis (Yes/No).*\n\n###### How it's computed:\n*Instantaneous gauge reading of the upgrade-recommended flag.*\n\n###### Reading it:\n*No is healthy; Yes means most peers run a newer version.*\n\n###### Healthy range:\n*No.*\n\n###### Watch for:\n*A Yes state, indicating the node risks falling out of step with the network.*\n\n###### Keywords:\n- **Peer** *(per node)* \u2014 another server this node holds a protocol connection to.\n- **Overlay** *(network-wide)* \u2014 the peer-to-peer mesh xrpld nodes form to gossip transactions, proposals and validations.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerPeerQualityGauge`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#peer)", "type": "stat", "gridPos": { "h": 8, - "w": 6, + "w": 24, "x": 0, - "y": 8 + "y": 24 }, "options": { "tooltip": { @@ -193,14 +226,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(peer_quality{metric=\"upgrade_recommended\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Upgrade\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(peer_quality{metric=\"upgrade_recommended\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Upgrade\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "none", "mappings": [ { @@ -241,13 +275,13 @@ }, { "title": "Resource Disconnects", - "description": "###### What this is:\n*Cumulative count of peers dropped for exceeding resource (load) limits.*\n\n###### How it's computed:\n*Instantaneous gauge reading of the resource-disconnect total over time.*\n\n###### Reading it:\n*A flat line is healthy; a rising line means peers are being dropped for overuse.*\n\n###### Healthy range:\n*Flat / near constant.*\n\n###### Watch for:\n*A steep climb, which flags aggressive or misbehaving peers being shed as backpressure.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerServerInfoGauge`", + "description": "###### What this is:\n*Cumulative count of peers dropped for exceeding resource (load) limits.*\n\n###### How it's computed:\n*Instantaneous gauge reading of the resource-disconnect total over time.*\n\n###### Reading it:\n*A flat line is healthy; a rising line means peers are being dropped for overuse.*\n\n###### Healthy range:\n*Flat / near constant.*\n\n###### Watch for:\n*A steep climb, which flags aggressive or misbehaving peers being shed as backpressure.*\n\n###### Keywords:\n- **Resource disconnect** *(per node)* \u2014 a peer dropped for exceeding resource/load limits \u2014 the node shedding abusive or overactive peers.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerServerInfoGauge`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#resource-disconnect)", "type": "timeseries", "gridPos": { "h": 8, - "w": 10, - "x": 6, - "y": 8 + "w": 24, + "x": 0, + "y": 32 }, "options": { "tooltip": { @@ -259,14 +293,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(server_info{metric=\"peer_disconnects_resources\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Disconnects\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(server_info{metric=\"peer_disconnects_resources\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Disconnects\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "short", "custom": { "axisLabel": "Disconnects", @@ -287,13 +322,13 @@ }, { "title": "Inbound vs Outbound Peers", - "description": "###### What this is:\n*Active inbound versus outbound peer connection counts.*\n\n###### How it's computed:\n*Instantaneous gauge readings of active inbound and outbound peer counts.*\n\n###### Reading it:\n*A balanced mix is healthy for connectivity and resilience.*\n\n###### Healthy range:\n*A mix of both; some outbound connections present.*\n\n###### Watch for:\n*All-inbound with no outbound, which usually means NAT or firewall issues block outbound peering.*\n\n###### Source:\n[PeerfinderManager.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/peerfinder/detail/PeerfinderManager.cpp)\n\n###### Function:\n`Logic Stats ctor`", + "description": "###### What this is:\n*Active inbound versus outbound peer connection counts.*\n\n###### How it's computed:\n*Instantaneous gauge readings of active inbound and outbound peer counts.*\n\n###### Reading it:\n*A balanced mix is healthy for connectivity and resilience.*\n\n###### Healthy range:\n*A mix of both; some outbound connections present.*\n\n###### Watch for:\n*All-inbound with no outbound, which usually means NAT or firewall issues block outbound peering.*\n\n###### Keywords:\n- **Peer** *(per node)* \u2014 another server this node holds a protocol connection to.\n- **Overlay** *(network-wide)* \u2014 the peer-to-peer mesh xrpld nodes form to gossip transactions, proposals and validations.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[PeerfinderManager.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/peerfinder/detail/PeerfinderManager.cpp)\n\n###### Function:\n`Logic Stats ctor`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#peer)", "type": "bargauge", "gridPos": { "h": 8, - "w": 8, - "x": 16, - "y": 8 + "w": 24, + "x": 0, + "y": 40 }, "options": { "orientation": "horizontal", @@ -307,20 +342,22 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(peer_finder_active_inbound_peers{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Inbound\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(peer_finder_active_inbound_peers{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Inbound\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(peer_finder_active_outbound_peers{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Outbound\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(peer_finder_active_outbound_peers{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Outbound\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "short", "thresholds": { "steps": [ @@ -371,6 +408,19 @@ "tags": ["network", "peer"], "templating": { "list": [ + { + "name": "DS_PROMETHEUS", + "type": "datasource", + "label": "Prometheus", + "query": "prometheus", + "regex": "", + "current": {}, + "hide": 0, + "refresh": 1, + "includeAll": false, + "multi": false, + "options": [] + }, { "name": "service_name", "label": "Service Name", @@ -379,7 +429,7 @@ "query": "label_values(service_name)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -399,7 +449,7 @@ "query": "label_values(deployment_environment)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -419,7 +469,7 @@ "query": "label_values(xrpl_network_type)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -499,7 +549,7 @@ "query": "label_values(target_info, service_instance_id)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -519,5 +569,5 @@ }, "title": "Peer Quality", "uid": "peer-quality", - "refresh": "5s" + "refresh": "30s" } diff --git a/docker/telemetry/grafana/dashboards/rpc-pathfinding.json b/docker/telemetry/grafana/dashboards/rpc-pathfinding.json index 6211e3e254..610a7965f0 100644 --- a/docker/telemetry/grafana/dashboards/rpc-pathfinding.json +++ b/docker/telemetry/grafana/dashboards/rpc-pathfinding.json @@ -1,8 +1,38 @@ { "annotations": { - "list": [] + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + }, + { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": false, + "iconColor": "rgb(70, 70, 70)", + "name": "Annotate perf-iac runs", + "target": { + "limit": 100, + "matchAny": false, + "tags": ["perf-iac"], + "type": "tags" + }, + "type": "tags" + } + ] }, - "description": "What this shows: RPC request throughput and response timing, pathfinding cost, gRPC read traffic, and resource-manager enforcement for this node.\nUse it to: Watch client request load and cost, and confirm resource limits are protecting the node.", + "description": "What this shows: RPC request throughput and response timing, pathfinding cost, gRPC read traffic, and resource-manager enforcement for this node. \u2014 Use it to: Watch client request load and cost, and confirm resource limits are protecting the node.", "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 1, @@ -11,7 +41,7 @@ "panels": [ { "title": "RPC Request Rate", - "description": "###### What this is:\n*How many RPC requests the server counts per second.*\n\n###### How it's computed:\n*Per-second rate of the RPC request counter over 5 minutes, per node.*\n\n###### Reading it:\n*A steady line proportional to client demand; cross-checks the trace-based RPC dashboard.*\n\n###### Healthy range:\n*Workload-dependent; tracks client activity.*\n\n###### Watch for:\n*A sudden surge above baseline, consistent with a client flooding the RPC endpoint.*\n\n###### Source:\n[ServerHandler.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/detail/ServerHandler.cpp)\n\n###### Function:\n`ServerHandler ctor`", + "description": "###### What this is:\n*How many RPC requests the server counts per second.*\n\n###### How it's computed:\n*Per-second rate of the RPC request counter over 5 minutes, per node.*\n\n###### Reading it:\n*A steady line proportional to client demand; cross-checks the trace-based RPC dashboard.*\n\n###### Healthy range:\n*Workload-dependent; tracks client activity.*\n\n###### Watch for:\n*A sudden surge above baseline, consistent with a client flooding the RPC endpoint.*\n\n###### Keywords:\n- **RPC command / method** *(per node)* \u2014 a named API request served by the node (e.g. account_info, ledger, submit), the unit RPC panels break down by.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[ServerHandler.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/detail/ServerHandler.cpp)\n\n###### Function:\n`ServerHandler ctor`\n\n###### References:\n[RPC command / method](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#rpc-command-method)", "type": "stat", "gridPos": { "h": 8, @@ -29,14 +59,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(rpc_requests_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Requests / Sec\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(rpc_requests_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Requests / Sec\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "reqps" }, "overrides": [] @@ -44,7 +75,7 @@ }, { "title": "RPC Response Time", - "description": "###### What this is:\n*The 95th-percentile end-to-end RPC response time, including HTTP handling.*\n\n###### How it's computed:\n*95th-percentile of measured response times over 5 minutes, per node.*\n\n###### Reading it:\n*Lower is better; this is broader than command-only latency.*\n\n###### Healthy range:\n*A few to tens of milliseconds; workload-dependent.*\n\n###### Watch for:\n*A sustained climb, indicating handler overload or slow downstream work.*\n\n###### Source:\n[ServerHandler.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/detail/ServerHandler.cpp)\n\n###### Function:\n`ServerHandler ctor`", + "description": "###### What this is:\n*The 95th-percentile end-to-end RPC response time, including HTTP handling.*\n\n###### How it's computed:\n*95th-percentile of measured response times over 5 minutes, per node.*\n\n###### Reading it:\n*Lower is better; this is broader than command-only latency.*\n\n###### Healthy range:\n*A few to tens of milliseconds; workload-dependent.*\n\n###### Watch for:\n*A sustained climb, indicating handler overload or slow downstream work.*\n\n###### Keywords:\n- **RPC command / method** *(per node)* \u2014 a named API request served by the node (e.g. account_info, ledger, submit), the unit RPC panels break down by.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[ServerHandler.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/detail/ServerHandler.cpp)\n\n###### Function:\n`ServerHandler ctor`\n\n###### References:\n[RPC command / method](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#rpc-command-method)", "type": "timeseries", "gridPos": { "h": 8, @@ -62,14 +93,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(histogram_quantile(0.95, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(rpc_time_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"P95 Response Time\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(histogram_quantile(0.95, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(rpc_time_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"P95 Response Time\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "ms", "custom": { "axisLabel": "Latency (ms)", @@ -84,7 +116,7 @@ }, { "title": "RPC Response Size", - "description": "\u26a0 Instrument mismatch \u2014 values unreliable. Response size is recorded through the millisecond-scaled event histogram (rpc_size_milliseconds_bucket), so byte values saturate at the top time bucket (5000) and the percentiles are not true byte sizes. A dedicated byte-unit histogram is needed to fix this; tracked separately. Treat this panel as indicative only until then.\n\n###### What this is:\n*The 95th-percentile size of RPC response payloads in bytes.*\n\n###### How it's computed:\n*95th-percentile of response payload sizes over the dashboard rate interval, per node.*\n\n###### Reading it:\n*Smaller is cheaper; large responses cost bandwidth and memory.*\n\n###### Healthy range:\n*Workload-dependent; small for status queries, large for bulk data queries.*\n\n###### Watch for:\n*Growth in large responses, consistent with expensive queries or API misuse.*\n\n###### Source:\n[ServerHandler.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/detail/ServerHandler.cpp)\n\n###### Function:\n`ServerHandler ctor`", + "description": "\u26a0 Instrument mismatch \u2014 values unreliable. Response size is recorded through the millisecond-scaled event histogram (rpc_size_milliseconds_bucket), so byte values saturate at the top time bucket (5000) and the percentiles are not true byte sizes. A dedicated byte-unit histogram is needed to fix this; tracked separately. Treat this panel as indicative only until then.\n\n###### What this is:\n*The 95th-percentile size of RPC response payloads in bytes.*\n\n###### How it's computed:\n*95th-percentile of response payload sizes over the dashboard rate interval, per node.*\n\n###### Reading it:\n*Smaller is cheaper; large responses cost bandwidth and memory.*\n\n###### Healthy range:\n*Workload-dependent; small for status queries, large for bulk data queries.*\n\n###### Watch for:\n*Growth in large responses, consistent with expensive queries or API misuse.*\n\n###### Keywords:\n- **RPC command / method** *(per node)* \u2014 a named API request served by the node (e.g. account_info, ledger, submit), the unit RPC panels break down by.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[ServerHandler.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/detail/ServerHandler.cpp)\n\n###### Function:\n`ServerHandler ctor`\n\n###### References:\n[RPC command / method](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#rpc-command-method)", "type": "timeseries", "gridPos": { "h": 8, @@ -102,14 +134,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(histogram_quantile(0.95, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(rpc_size_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"P95 Response Size\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(histogram_quantile(0.95, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(rpc_size_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"P95 Response Size\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "decbytes", "custom": { "axisLabel": "Size (Bytes)", @@ -124,7 +157,7 @@ }, { "title": "RPC Response Time Distribution", - "description": "###### What this is:\n*RPC response time at the 90th, 95th, and 99th percentiles together.*\n\n###### How it's computed:\n*Three response-time quantiles over 5 minutes, per node.*\n\n###### Reading it:\n*Lower is better; the gap between P90 and P99 shows the long tail.*\n\n###### Healthy range:\n*Percentiles clustered low; workload-dependent.*\n\n###### Watch for:\n*A widening P99, revealing long-tail or bimodal slow requests.*\n\n###### Source:\n[ServerHandler.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/detail/ServerHandler.cpp)\n\n###### Function:\n`ServerHandler ctor`", + "description": "###### What this is:\n*RPC response time at the 90th, 95th, and 99th percentiles together.*\n\n###### How it's computed:\n*Three response-time quantiles over 5 minutes, per node.*\n\n###### Reading it:\n*Lower is better; the gap between P90 and P99 shows the long tail.*\n\n###### Healthy range:\n*Percentiles clustered low; workload-dependent.*\n\n###### Watch for:\n*A widening P99, revealing long-tail or bimodal slow requests.*\n\n###### Keywords:\n- **RPC command / method** *(per node)* \u2014 a named API request served by the node (e.g. account_info, ledger, submit), the unit RPC panels break down by.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[ServerHandler.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/detail/ServerHandler.cpp)\n\n###### Function:\n`ServerHandler ctor`\n\n###### References:\n[RPC command / method](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#rpc-command-method)", "type": "timeseries", "gridPos": { "h": 8, @@ -142,26 +175,29 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(histogram_quantile(0.9, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(rpc_time_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"P90\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(histogram_quantile(0.9, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(rpc_time_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"P90\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(histogram_quantile(0.95, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(rpc_time_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"P95\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(histogram_quantile(0.95, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(rpc_time_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"P95\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(histogram_quantile(0.99, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(rpc_time_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"P99\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(histogram_quantile(0.99, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(rpc_time_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"P99\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "ms", "custom": { "axisLabel": "Latency (ms)", @@ -176,7 +212,7 @@ }, { "title": "Pathfinding Fast Duration", - "description": "###### What this is:\n*The 95th-percentile time of the fast pathfinding search.*\n\n###### How it's computed:\n*95th-percentile of fast pathfinding durations over 5 minutes, per node.*\n\n###### Reading it:\n*Lower is better; fast mode trades accuracy for speed.*\n\n###### Healthy range:\n*Typically a few to tens of milliseconds; workload-dependent.*\n\n###### Watch for:\n*A rising trend, indicating heavy pathfinding demand or an expensive order book.*\n\n###### Source:\n[PathRequestManager.h](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/detail/PathRequestManager.h)\n\n###### Function:\n`ctor`", + "description": "###### What this is:\n*The 95th-percentile time of the fast pathfinding search.*\n\n###### How it's computed:\n*95th-percentile of fast pathfinding durations over 5 minutes, per node.*\n\n###### Reading it:\n*Lower is better; fast mode trades accuracy for speed.*\n\n###### Healthy range:\n*Typically a few to tens of milliseconds; workload-dependent.*\n\n###### Watch for:\n*A rising trend, indicating heavy pathfinding demand or an expensive order book.*\n\n###### Keywords:\n- **Pathfinding (fast / full)** *(per node)* \u2014 searching for payment paths through intermediaries; fast mode trades accuracy for speed, full is exhaustive.\n- **Order book** *(network-wide)* \u2014 the ledger's list of standing offers to trade a currency pair on the decentralized exchange.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[PathRequestManager.h](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/detail/PathRequestManager.h)\n\n###### Function:\n`ctor`\n\n###### References:\n[Pathfinding (fast / full)](https://xrpl.org/docs/concepts/tokens/fungible-tokens/paths) \u00b7 [Order book](https://xrpl.org/docs/concepts/tokens/decentralized-exchange) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#pathfinding-fast-full)", "type": "timeseries", "gridPos": { "h": 8, @@ -194,14 +230,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(histogram_quantile(0.95, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(pathfind_fast_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"P95 Fast Pathfind\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(histogram_quantile(0.95, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(pathfind_fast_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"P95 Fast Pathfind\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "ms", "custom": { "axisLabel": "Duration (ms)", @@ -216,7 +253,7 @@ }, { "title": "Pathfinding Full Duration", - "description": "###### What this is:\n*The 95th-percentile time of the full, exhaustive pathfinding search.*\n\n###### How it's computed:\n*95th-percentile of full pathfinding durations over 5 minutes, per node.*\n\n###### Reading it:\n*Lower is better; full mode is much more expensive than fast mode.*\n\n###### Healthy range:\n*Tens to hundreds of milliseconds; workload-dependent.*\n\n###### Watch for:\n*Sustained high durations, consistent with pathfinding-heavy clients straining the node.*\n\n###### Source:\n[PathRequestManager.h](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/detail/PathRequestManager.h)\n\n###### Function:\n`ctor`", + "description": "###### What this is:\n*The 95th-percentile time of the full, exhaustive pathfinding search.*\n\n###### How it's computed:\n*95th-percentile of full pathfinding durations over 5 minutes, per node.*\n\n###### Reading it:\n*Lower is better; full mode is much more expensive than fast mode.*\n\n###### Healthy range:\n*Tens to hundreds of milliseconds; workload-dependent.*\n\n###### Watch for:\n*Sustained high durations, consistent with pathfinding-heavy clients straining the node.*\n\n###### Keywords:\n- **Pathfinding (fast / full)** *(per node)* \u2014 searching for payment paths through intermediaries; fast mode trades accuracy for speed, full is exhaustive.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[PathRequestManager.h](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/detail/PathRequestManager.h)\n\n###### Function:\n`ctor`\n\n###### References:\n[Pathfinding (fast / full)](https://xrpl.org/docs/concepts/tokens/fungible-tokens/paths) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#pathfinding-fast-full)", "type": "timeseries", "gridPos": { "h": 8, @@ -234,14 +271,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(histogram_quantile(0.95, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(pathfind_full_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"P95 Full Pathfind\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(histogram_quantile(0.95, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(pathfind_full_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[5m]))), \"series\", \"P95 Full Pathfind\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "ms", "custom": { "axisLabel": "Duration (ms)", @@ -256,7 +294,7 @@ }, { "title": "Resource Warnings Rate", - "description": "###### What this is:\n*How often the resource manager warns a peer or client for excessive usage, per second.*\n\n###### How it's computed:\n*Per-second rate of resource-warning events over 5 minutes, per node.*\n\n###### Reading it:\n*Green near zero, yellow above 0.1/sec, red above 1/sec.*\n\n###### Healthy range:\n*Near zero in normal operation.*\n\n###### Watch for:\n*A rising rate, consistent with aggressive clients that may need throttling.*\n\n###### Source:\n[Logic.h](https://github.com/XRPLF/rippled/blob/develop/include/xrpl/resource/detail/Logic.h)\n\n###### Function:\n`Logic::Stats ctor`", + "description": "###### What this is:\n*How often the resource manager warns a peer or client for excessive usage, per second.*\n\n###### How it's computed:\n*Per-second rate of resource-warning events over 5 minutes, per node.*\n\n###### Reading it:\n*Green near zero, yellow above 0.1/sec, red above 1/sec.*\n\n###### Healthy range:\n*Near zero in normal operation.*\n\n###### Watch for:\n*A rising rate, consistent with aggressive clients that may need throttling.*\n\n###### Keywords:\n- **Resource drops / warnings** *(per node)* \u2014 the resource manager warning (then dropping/blocking) a peer or client for excessive usage.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[Logic.h](https://github.com/XRPLF/rippled/blob/develop/include/xrpl/resource/detail/Logic.h)\n\n###### Function:\n`Logic::Stats ctor`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#resource-drops-warnings)", "type": "stat", "gridPos": { "h": 8, @@ -274,14 +312,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(warn_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Warnings / Sec\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(warn_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Warnings / Sec\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: warnings/s", "thresholds": { "steps": [ @@ -305,7 +344,7 @@ }, { "title": "Resource Drops Rate", - "description": "###### What this is:\n*How often the resource manager drops or blocks a peer or client for abuse, per second.*\n\n###### How it's computed:\n*Per-second rate of resource-drop events over 5 minutes, per node.*\n\n###### Reading it:\n*Green near zero, yellow above 0.01/sec, red above 0.1/sec.*\n\n###### Healthy range:\n*Zero when no abusive consumers are present.*\n\n###### Watch for:\n*Non-zero values, meaning the node is actively rejecting abusive connections.*\n\n###### Source:\n[Logic.h](https://github.com/XRPLF/rippled/blob/develop/include/xrpl/resource/detail/Logic.h)\n\n###### Function:\n`Logic::Stats ctor`", + "description": "###### What this is:\n*How often the resource manager drops or blocks a peer or client for abuse, per second.*\n\n###### How it's computed:\n*Per-second rate of resource-drop events over 5 minutes, per node.*\n\n###### Reading it:\n*Green near zero, yellow above 0.01/sec, red above 0.1/sec.*\n\n###### Healthy range:\n*Zero when no abusive consumers are present.*\n\n###### Watch for:\n*Non-zero values, meaning the node is actively rejecting abusive connections.*\n\n###### Keywords:\n- **Resource drops / warnings** *(per node)* \u2014 the resource manager warning (then dropping/blocking) a peer or client for excessive usage.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in xrpld code as a native metric (beast::insight); the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[Logic.h](https://github.com/XRPLF/rippled/blob/develop/include/xrpl/resource/detail/Logic.h)\n\n###### Function:\n`Logic::Stats ctor`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#resource-drops-warnings)", "type": "stat", "gridPos": { "h": 8, @@ -323,14 +362,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(drop_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Drops / Sec\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(drop_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]), \"series\", \"Drops / Sec\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: drops/s", "thresholds": { "steps": [ @@ -354,7 +394,7 @@ }, { "title": "gRPC Request Rate by Method (Spans)", - "description": "###### What this is:\n*How many gRPC calls of each method the node serves per second.*\n\n###### How it's computed:\n*Per-second rate of gRPC calls over 5 minutes, grouped by method and node.*\n\n###### Reading it:\n*Non-zero only when reporting/Clio-style clients use the gRPC API.*\n\n###### Healthy range:\n*Workload-dependent; zero without gRPC traffic.*\n\n###### Watch for:\n*A single method spiking, consistent with a heavy or misbehaving gRPC consumer.*\n\n###### Source:\n[GRPCServer.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/main/GRPCServer.cpp)\n\n###### Function:\n`GRPCServerImpl::CallData::process`", + "description": "###### What this is:\n*How many gRPC calls of each method the node serves per second.*\n\n###### How it's computed:\n*Per-second rate of gRPC calls over 5 minutes, grouped by method and node.*\n\n###### Reading it:\n*Non-zero only when reporting/Clio-style clients use the gRPC API.*\n\n###### Healthy range:\n*Workload-dependent; zero without gRPC traffic.*\n\n###### Watch for:\n*A single method spiking, consistent with a heavy or misbehaving gRPC consumer.*\n\n###### Keywords:\n- **RPC command / method** *(per node)* \u2014 a named API request served by the node (e.g. account_info, ledger, submit), the unit RPC panels break down by.\n- **Clio / reporting client** *(per node)* \u2014 an external read-scaling service (Clio) that serves history/API queries, often via the gRPC interface.\n- **gRPC** *(per node)* \u2014 a binary RPC interface used mainly by reporting/Clio-style clients, separate from HTTP/WebSocket.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[GRPCServer.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/main/GRPCServer.cpp)\n\n###### Function:\n`GRPCServerImpl::CallData::process`\n\n###### References:\n[RPC command / method](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#rpc-command-method)", "type": "timeseries", "gridPos": { "h": 8, @@ -372,14 +412,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (method, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", method=~\"$grpc_method\", span_name=~\"grpc\\\\..*\"}[$__rate_interval])), \"series\", \"$1\", \"method\", \"(.*)\")" + "expr": "label_replace(label_join(label_replace(sum by (method, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", method=~\"$grpc_method\", span_name=~\"grpc\\\\..*\"}[$__rate_interval])), \"series\", \"$1\", \"method\", \"(.*)\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: calls/s", "custom": { "axisLabel": "Calls / Sec", @@ -394,7 +435,7 @@ }, { "title": "gRPC Latency P95 by Method (Spans)", - "description": "###### What this is:\n*The 95th-percentile latency of each gRPC method.*\n\n###### How it's computed:\n*95th-percentile of gRPC call durations over 5 minutes, grouped by method and node.*\n\n###### Reading it:\n*Lower is better; identifies slow gRPC read paths.*\n\n###### Healthy range:\n*Workload-dependent; scales with ledger data volume served.*\n\n###### Watch for:\n*A method whose latency climbs, indicating an expensive read path.*\n\n###### Source:\n[GRPCServer.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/main/GRPCServer.cpp)\n\n###### Function:\n`GRPCServerImpl::CallData::process`", + "description": "###### What this is:\n*The 95th-percentile latency of each gRPC method.*\n\n###### How it's computed:\n*95th-percentile of gRPC call durations over 5 minutes, grouped by method and node.*\n\n###### Reading it:\n*Lower is better; identifies slow gRPC read paths.*\n\n###### Healthy range:\n*Workload-dependent; scales with ledger data volume served.*\n\n###### Watch for:\n*A method whose latency climbs, indicating an expensive read path.*\n\n###### Keywords:\n- **gRPC** *(per node)* \u2014 a binary RPC interface used mainly by reporting/Clio-style clients, separate from HTTP/WebSocket.\n- **RPC command / method** *(per node)* \u2014 a named API request served by the node (e.g. account_info, ledger, submit), the unit RPC panels break down by.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[GRPCServer.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/main/GRPCServer.cpp)\n\n###### Function:\n`GRPCServerImpl::CallData::process`\n\n###### References:\n[RPC command / method](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#grpc)", "type": "timeseries", "gridPos": { "h": 8, @@ -412,14 +453,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(histogram_quantile(0.95, sum by (le, method, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_duration_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", method=~\"$grpc_method\", span_name=~\"grpc\\\\..*\"}[5m]))), \"series\", \"$1\", \"method\", \"(.*)\")" + "expr": "label_replace(label_join(label_replace(histogram_quantile(0.95, sum by (le, method, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_duration_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", method=~\"$grpc_method\", span_name=~\"grpc\\\\..*\"}[5m]))), \"series\", \"$1\", \"method\", \"(.*)\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "ms", "custom": { "axisLabel": "Duration (ms)", @@ -434,7 +476,7 @@ }, { "title": "gRPC Error Rate by Status (Spans)", - "description": "###### What this is:\n*gRPC call rate broken down by outcome status (success or error).*\n\n###### How it's computed:\n*Per-second rate of gRPC calls over 5 minutes, grouped by status and node.*\n\n###### Reading it:\n*Success should dominate; the error rate should stay low.*\n\n###### Healthy range:\n*Workload-dependent; errors near the floor.*\n\n###### Watch for:\n*A rising error rate, indicating gRPC clients hitting failures.*\n\n###### Source:\n[GRPCServer.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/main/GRPCServer.cpp)\n\n###### Function:\n`GRPCServerImpl::CallData::process`", + "description": "###### What this is:\n*gRPC call rate broken down by outcome status (success or error).*\n\n###### How it's computed:\n*Per-second rate of gRPC calls over 5 minutes, grouped by status and node.*\n\n###### Reading it:\n*Success should dominate; the error rate should stay low.*\n\n###### Healthy range:\n*Workload-dependent; errors near the floor.*\n\n###### Watch for:\n*A rising error rate, indicating gRPC clients hitting failures.*\n\n###### Keywords:\n- **gRPC** *(per node)* \u2014 a binary RPC interface used mainly by reporting/Clio-style clients, separate from HTTP/WebSocket.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[GRPCServer.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/main/GRPCServer.cpp)\n\n###### Function:\n`GRPCServerImpl::CallData::process`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#grpc)", "type": "timeseries", "gridPos": { "h": 8, @@ -452,14 +494,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (grpc_status, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=~\"grpc\\\\..*\", grpc_status!=\"\"}[$__rate_interval])), \"series\", \"$1\", \"grpc_status\", \"(.*)\")" + "expr": "label_replace(label_join(label_replace(sum by (grpc_status, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=~\"grpc\\\\..*\", grpc_status!=\"\"}[$__rate_interval])), \"series\", \"$1\", \"grpc_status\", \"(.*)\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: calls/s", "custom": { "axisLabel": "Calls / Sec", @@ -474,7 +517,7 @@ }, { "title": "Pathfinding Compute Duration (Spans)", - "description": "###### What this is:\n*The 95th-percentile time to compute paths for a single request.*\n\n###### How it's computed:\n*95th-percentile of path-computation durations over 5 minutes, per node.*\n\n###### Reading it:\n*Lower is better; complements the fast/full pathfinding timers with span-level detail.*\n\n###### Healthy range:\n*Workload-dependent; grows with order-book depth and request complexity.*\n\n###### Watch for:\n*A rising trend under pathfinding load, indicating expensive path computation.*\n\n###### Source:\n[PathRequest.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/detail/PathRequest.cpp)\n\n###### Function:\n`PathRequest::doUpdate`", + "description": "###### What this is:\n*The 95th-percentile time to compute paths for a single request.*\n\n###### How it's computed:\n*95th-percentile of path-computation durations over 5 minutes, per node.*\n\n###### Reading it:\n*Lower is better; complements the fast/full pathfinding timers with span-level detail.*\n\n###### Healthy range:\n*Workload-dependent; grows with order-book depth and request complexity.*\n\n###### Watch for:\n*A rising trend under pathfinding load, indicating expensive path computation.*\n\n###### Keywords:\n- **Batch vs single RPC** *(per node)* \u2014 whether an RPC request carried several commands (batch) or one (single); batch is used by bulk clients.\n- **Pathfinding (fast / full)** *(per node)* \u2014 searching for payment paths through intermediaries; fast mode trades accuracy for speed, full is exhaustive.\n- **Order book** *(network-wide)* \u2014 the ledger's list of standing offers to trade a currency pair on the decentralized exchange.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[PathRequest.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/detail/PathRequest.cpp)\n\n###### Function:\n`PathRequest::doUpdate`\n\n###### References:\n[Pathfinding (fast / full)](https://xrpl.org/docs/concepts/tokens/fungible-tokens/paths) \u00b7 [Order book](https://xrpl.org/docs/concepts/tokens/decentralized-exchange) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#batch-vs-single-rpc)", "type": "timeseries", "gridPos": { "h": 8, @@ -492,14 +535,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(histogram_quantile(0.95, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_duration_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"pathfind.compute\"}[5m]))), \"series\", \"P95 Compute\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(histogram_quantile(0.95, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_duration_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"pathfind.compute\"}[5m]))), \"series\", \"P95 Compute\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "ms", "custom": { "axisLabel": "Duration (ms)", @@ -514,7 +558,7 @@ }, { "title": "Pathfinding Request & Discovery Rate (Spans)", - "description": "###### What this is:\n*The rate of client path requests versus path-discovery passes.*\n\n###### How it's computed:\n*Per-second rates of path requests and discovery passes over 5 minutes, per node.*\n\n###### Reading it:\n*Discovery cost tracks request demand; useful for subscription-heavy nodes.*\n\n###### Healthy range:\n*Workload-dependent; both zero without pathfinding clients.*\n\n###### Watch for:\n*Discovery rate climbing out of proportion to requests, a cost driver for subscription-heavy nodes.*\n\n###### Source:\n[PathFind.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/handlers/orderbook/PathFind.cpp) \u00b7 [PathRequest.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/detail/PathRequest.cpp)\n\n###### Function:\n`doPathFind ; PathRequest::findPaths`", + "description": "###### What this is:\n*The rate of client path requests versus path-discovery passes.*\n\n###### How it's computed:\n*Per-second rates of path requests and discovery passes over 5 minutes, per node.*\n\n###### Reading it:\n*Discovery cost tracks request demand; useful for subscription-heavy nodes.*\n\n###### Healthy range:\n*Workload-dependent; both zero without pathfinding clients.*\n\n###### Watch for:\n*Discovery rate climbing out of proportion to requests, a cost driver for subscription-heavy nodes.*\n\n###### Keywords:\n- **Path request / discovery** *(per node)* \u2014 a client's ongoing pathfinding subscription (request) and the periodic path-refresh passes (discovery).\n- **Pathfinding (fast / full)** *(per node)* \u2014 searching for payment paths through intermediaries; fast mode trades accuracy for speed, full is exhaustive.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[PathFind.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/handlers/orderbook/PathFind.cpp) \u00b7 [PathRequest.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/detail/PathRequest.cpp)\n\n###### Function:\n`doPathFind ; PathRequest::findPaths`\n\n###### References:\n[Path request / discovery](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods/path-and-order-book-methods/path_find) \u00b7 [Pathfinding (fast / full)](https://xrpl.org/docs/concepts/tokens/fungible-tokens/paths) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#path-request-discovery)", "type": "timeseries", "gridPos": { "h": 8, @@ -532,20 +576,22 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"pathfind.request\"}[$__rate_interval])), \"series\", \"Requests / Sec\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"pathfind.request\"}[$__rate_interval])), \"series\", \"Requests / Sec\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"pathfind.discover\"}[$__rate_interval])), \"series\", \"Discoveries / Sec\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"pathfind.discover\"}[$__rate_interval])), \"series\", \"Discoveries / Sec\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: operations/s", "custom": { "axisLabel": "Operations / Sec", @@ -563,6 +609,19 @@ "tags": ["rpc", "pathfinding"], "templating": { "list": [ + { + "name": "DS_PROMETHEUS", + "type": "datasource", + "label": "Prometheus", + "query": "prometheus", + "regex": "", + "current": {}, + "hide": 0, + "refresh": 1, + "includeAll": false, + "multi": false, + "options": [] + }, { "name": "service_name", "label": "Service Name", @@ -571,7 +630,7 @@ "query": "label_values(service_name)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -591,7 +650,7 @@ "query": "label_values(deployment_environment)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -611,7 +670,7 @@ "query": "label_values(xrpl_network_type)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -691,7 +750,7 @@ "query": "label_values(target_info, service_instance_id)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -711,7 +770,7 @@ "query": "label_values(span_calls_total{span_name=~\"grpc\\\\..*\"}, method)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -731,5 +790,5 @@ }, "title": "RPC & Pathfinding", "uid": "rpc-pathfinding", - "refresh": "5s" + "refresh": "30s" } diff --git a/docker/telemetry/grafana/dashboards/rpc-performance.json b/docker/telemetry/grafana/dashboards/rpc-performance.json index 305aa4b5f7..a378b29e40 100644 --- a/docker/telemetry/grafana/dashboards/rpc-performance.json +++ b/docker/telemetry/grafana/dashboards/rpc-performance.json @@ -1,6 +1,36 @@ { "annotations": { - "list": [] + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + }, + { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": false, + "iconColor": "rgb(70, 70, 70)", + "name": "Annotate perf-iac runs", + "target": { + "limit": 100, + "matchAny": false, + "tags": ["perf-iac"], + "type": "tags" + }, + "type": "tags" + } + ] }, "editable": true, "fiscalYearStartMonth": 0, @@ -23,7 +53,7 @@ }, { "title": "RPC Request Rate by Command", - "description": "###### What this is:\n*How many times each RPC command runs per second.*\n\n###### How it's computed:\n*Per-second rate of command executions over 5 minutes, grouped by command name and node.*\n\n###### Reading it:\n*Compare bars/lines across commands to see which endpoints drive load.*\n\n###### Healthy range:\n*Workload-dependent; dominated by whichever commands clients call most.*\n\n###### Watch for:\n*A single command spiking far above its norm, consistent with a client hammering one endpoint.*\n\n###### Source:\n[RPCHandler.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/detail/RPCHandler.cpp)\n\n###### Function:\n`callMethod`", + "description": "###### What this is:\n*How many times each RPC command runs per second.*\n\n###### How it's computed:\n*Per-second rate of command executions over 5 minutes, grouped by command name and node.*\n\n###### Reading it:\n*Compare bars/lines across commands to see which endpoints drive load.*\n\n###### Healthy range:\n*Workload-dependent; dominated by whichever commands clients call most.*\n\n###### Watch for:\n*A single command spiking far above its norm, consistent with a client hammering one endpoint.*\n\n###### Keywords:\n- **RPC command / method** *(per node)* \u2014 a named API request served by the node (e.g. account_info, ledger, submit), the unit RPC panels break down by.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[RPCHandler.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/detail/RPCHandler.cpp)\n\n###### Function:\n`callMethod`\n\n###### References:\n[RPC command / method](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#rpc-command-method)", "type": "timeseries", "gridPos": { "h": 8, @@ -41,14 +71,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (command, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", command=~\"$command\", span_name=~\"rpc.command.*\"}[$__rate_interval])), \"series\", \"$1\", \"command\", \"(.*)\")" + "expr": "label_replace(label_join(label_replace(sum by (command, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", command=~\"$command\", span_name=~\"rpc.command.*\"}[$__rate_interval])), \"series\", \"$1\", \"command\", \"(.*)\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "reqps", "custom": { "axisLabel": "Requests / Sec", @@ -64,7 +95,7 @@ }, { "title": "RPC Latency P95 by Command", - "description": "###### What this is:\n*The 95th-percentile response time for each RPC command.*\n\n###### How it's computed:\n*95th-percentile of per-command execution durations over 5 minutes, grouped by command and node.*\n\n###### Reading it:\n*Lower is better; watch the slowest commands.*\n\n###### Healthy range:\n*Fast lookups a few ms; heavy queries tens to hundreds of ms. Workload-dependent.*\n\n###### Watch for:\n*A command whose latency climbs steadily, pointing to an expensive or degrading query path.*\n\n###### Source:\n[RPCHandler.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/detail/RPCHandler.cpp)\n\n###### Function:\n`callMethod`", + "description": "###### What this is:\n*The 95th-percentile response time for each RPC command.*\n\n###### How it's computed:\n*95th-percentile of per-command execution durations over 5 minutes, grouped by command and node.*\n\n###### Reading it:\n*Lower is better; watch the slowest commands.*\n\n###### Healthy range:\n*Fast lookups a few ms; heavy queries tens to hundreds of ms. Workload-dependent.*\n\n###### Watch for:\n*A command whose latency climbs steadily, pointing to an expensive or degrading query path.*\n\n###### Keywords:\n- **RPC command / method** *(per node)* \u2014 a named API request served by the node (e.g. account_info, ledger, submit), the unit RPC panels break down by.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[RPCHandler.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/detail/RPCHandler.cpp)\n\n###### Function:\n`callMethod`\n\n###### References:\n[RPC command / method](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#rpc-command-method)", "type": "timeseries", "gridPos": { "h": 8, @@ -82,14 +113,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(histogram_quantile(0.95, sum by (le, command, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_duration_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", command=~\"$command\", span_name=~\"rpc.command.*\"}[5m]))), \"series\", \"P95 $1\", \"command\", \"(.*)\")" + "expr": "label_replace(label_join(label_replace(histogram_quantile(0.95, sum by (le, command, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_duration_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", command=~\"$command\", span_name=~\"rpc.command.*\"}[5m]))), \"series\", \"P95 $1\", \"command\", \"(.*)\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "ms", "custom": { "axisLabel": "Latency (ms)", @@ -105,7 +137,7 @@ }, { "title": "RPC Error Rate", - "description": "###### What this is:\n*The percentage of each command's calls that finished with an error.*\n\n###### How it's computed:\n*Error calls divided by total calls per command over 5 minutes, expressed as a percent.*\n\n###### Reading it:\n*Green under 1%, yellow 1-5%, red above 5%.*\n\n###### Healthy range:\n*Below 1% per command in normal operation.*\n\n###### Watch for:\n*A command sitting red, indicating malformed input, overload, or a broken handler.*\n\n###### Source:\n[RPCHandler.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/detail/RPCHandler.cpp)\n\n###### Function:\n`callMethod`", + "description": "###### What this is:\n*The percentage of each command's calls that finished with an error.*\n\n###### How it's computed:\n*Error calls divided by total calls per command over 5 minutes, expressed as a percent.*\n\n###### Reading it:\n*Green under 1%, yellow 1-5%, red above 5%.*\n\n###### Healthy range:\n*Below 1% per command in normal operation.*\n\n###### Watch for:\n*A command sitting red, indicating malformed input, overload, or a broken handler.*\n\n###### Keywords:\n- **RPC command / method** *(per node)* \u2014 a named API request served by the node (e.g. account_info, ledger, submit), the unit RPC panels break down by.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[RPCHandler.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/detail/RPCHandler.cpp)\n\n###### Function:\n`callMethod`\n\n###### References:\n[RPC command / method](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#rpc-command-method)", "type": "bargauge", "gridPos": { "h": 8, @@ -123,14 +155,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (command, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", command=~\"$command\", span_name=~\"rpc.command.*\", status_code=\"STATUS_CODE_ERROR\"}[$__rate_interval])) / sum by (command, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", command=~\"$command\", span_name=~\"rpc.command.*\"}[$__rate_interval])) * 100, \"series\", \"$1\", \"command\", \"(.*)\")" + "expr": "label_replace(label_join(label_replace(sum by (command, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", command=~\"$command\", span_name=~\"rpc.command.*\", status_code=\"STATUS_CODE_ERROR\"}[$__rate_interval])) / sum by (command, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", command=~\"$command\", span_name=~\"rpc.command.*\"}[$__rate_interval])) * 100, \"series\", \"$1\", \"command\", \"(.*)\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "percent", "thresholds": { "steps": [ @@ -155,7 +188,7 @@ }, { "title": "RPC Latency Heatmap", - "description": "###### What this is:\n*The full distribution of RPC response times over the window.*\n\n###### How it's computed:\n*Counts of requests in each latency band per 5-minute window, shown as color density.*\n\n###### Reading it:\n*A tight low band is healthy; multiple bands mean mixed fast/slow requests.*\n\n###### Healthy range:\n*Most mass in the low-latency bands; workload-dependent.*\n\n###### Watch for:\n*A distinct high-latency cluster (bimodal behavior) that percentiles alone would hide.*\n\n###### Source:\n[RPCHandler.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/detail/RPCHandler.cpp)\n\n###### Function:\n`callMethod`", + "description": "###### What this is:\n*The full distribution of RPC response times over the window.*\n\n###### How it's computed:\n*Counts of requests in each latency band per 5-minute window, shown as color density.*\n\n###### Reading it:\n*A tight low band is healthy; multiple bands mean mixed fast/slow requests.*\n\n###### Healthy range:\n*Most mass in the low-latency bands; workload-dependent.*\n\n###### Watch for:\n*A distinct high-latency cluster (bimodal behavior) that percentiles alone would hide.*\n\n###### Keywords:\n- **RPC command / method** *(per node)* \u2014 a named API request served by the node (e.g. account_info, ledger, submit), the unit RPC panels break down by.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[RPCHandler.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/detail/RPCHandler.cpp)\n\n###### Function:\n`callMethod`\n\n###### References:\n[RPC command / method](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#rpc-command-method)", "type": "heatmap", "gridPos": { "h": 8, @@ -177,7 +210,8 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, "expr": "sum(increase(span_duration_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", command=~\"$command\", span_name=~\"rpc.command.*\"}[5m])) by (le)", "legendFormat": "{{le}}", @@ -193,7 +227,7 @@ }, { "title": "Overall RPC Throughput", - "description": "###### What this is:\n*Two layers of the request pipeline: connections accepted by the HTTP handler versus requests parsed and dispatched.*\n\n###### How it's computed:\n*Per-second rates of the outer request-accept layer and the inner processing layer over 5 minutes, per node.*\n\n###### Reading it:\n*The two lines should track each other closely.*\n\n###### Healthy range:\n*Workload-dependent; both lines roughly equal.*\n\n###### Watch for:\n*A gap between accept and process, meaning requests are queued or rejected before dispatch.*\n\n###### Source:\n[ServerHandler.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/detail/ServerHandler.cpp)\n\n###### Function:\n`ServerHandler::processSession ; ServerHandler::processRequest`", + "description": "###### What this is:\n*Two layers of the request pipeline: connections accepted by the HTTP handler versus requests parsed and dispatched.*\n\n###### How it's computed:\n*Per-second rates of the outer request-accept layer and the inner processing layer over 5 minutes, per node.*\n\n###### Reading it:\n*The two lines should track each other closely.*\n\n###### Healthy range:\n*Workload-dependent; both lines roughly equal.*\n\n###### Watch for:\n*A gap between accept and process, meaning requests are queued or rejected before dispatch.*\n\n###### Keywords:\n- **RPC command / method** *(per node)* \u2014 a named API request served by the node (e.g. account_info, ledger, submit).\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[ServerHandler.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/detail/ServerHandler.cpp)\n\n###### Function:\n`ServerHandler::processSession ; ServerHandler::processRequest`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md)", "type": "timeseries", "gridPos": { "h": 8, @@ -211,20 +245,22 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", command=~\"$command\", span_name=\"rpc.http_request\"}[$__rate_interval])), \"series\", \"rpc.http_request / Sec\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", command=~\"$command\", span_name=\"rpc.http_request\"}[$__rate_interval])), \"series\", \"rpc.http_request / Sec\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", command=~\"$command\", span_name=\"rpc.process\"}[$__rate_interval])), \"series\", \"rpc.process / Sec\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", command=~\"$command\", span_name=\"rpc.process\"}[$__rate_interval])), \"series\", \"rpc.process / Sec\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "reqps", "custom": { "axisLabel": "Requests / Sec", @@ -240,7 +276,7 @@ }, { "title": "RPC Success vs Error", - "description": "###### What this is:\n*Aggregate rate of successful versus failed RPC commands across all command types.*\n\n###### How it's computed:\n*Per-second rates of ok-status and error-status command completions over 5 minutes, per node.*\n\n###### Reading it:\n*Successful commands report an ok status and should dominate; the error line should stay near the floor.*\n\n###### Healthy range:\n*Error line near zero relative to success; workload-dependent.*\n\n###### Watch for:\n*A sustained error line, which warrants drilling into the per-command breakdown.*\n\n###### Source:\n[RPCHandler.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/detail/RPCHandler.cpp)\n\n###### Function:\n`callMethod`", + "description": "###### What this is:\n*Aggregate rate of successful versus failed RPC commands across all command types.*\n\n###### How it's computed:\n*Per-second rates of ok-status and error-status command completions over 5 minutes, per node.*\n\n###### Reading it:\n*Successful commands report an ok status and should dominate; the error line should stay near the floor.*\n\n###### Healthy range:\n*Error line near zero relative to success; workload-dependent.*\n\n###### Watch for:\n*A sustained error line, which warrants drilling into the per-command breakdown.*\n\n###### Keywords:\n- **RPC command / method** *(per node)* \u2014 a named API request served by the node (e.g. account_info, ledger, submit), the unit RPC panels break down by.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[RPCHandler.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/detail/RPCHandler.cpp)\n\n###### Function:\n`callMethod`\n\n###### References:\n[RPC command / method](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#rpc-command-method)", "type": "timeseries", "gridPos": { "h": 8, @@ -258,20 +294,22 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", command=~\"$command\", span_name=~\"rpc.command.*\", status_code=\"STATUS_CODE_OK\"}[$__rate_interval])), \"series\", \"Success\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", command=~\"$command\", span_name=~\"rpc.command.*\", status_code=\"STATUS_CODE_OK\"}[$__rate_interval])), \"series\", \"Success\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", command=~\"$command\", span_name=~\"rpc.command.*\", status_code=\"STATUS_CODE_ERROR\"}[$__rate_interval])), \"series\", \"Error\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", command=~\"$command\", span_name=~\"rpc.command.*\", status_code=\"STATUS_CODE_ERROR\"}[$__rate_interval])), \"series\", \"Error\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: commands/s", "custom": { "axisLabel": "Commands / Sec", @@ -287,7 +325,7 @@ }, { "title": "Top Commands by Volume", - "description": "###### What this is:\n*The ten most-called RPC commands over the recent window.*\n\n###### How it's computed:\n*Total invocation counts per command over the last 5 minutes, ranked and capped at ten.*\n\n###### Reading it:\n*The top bars are the hottest endpoints driving node load.*\n\n###### Healthy range:\n*Workload-dependent; typically led by lightweight status queries.*\n\n###### Watch for:\n*An unexpected command dominating the ranking, consistent with automated abuse.*\n\n###### Source:\n[RPCHandler.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/detail/RPCHandler.cpp)\n\n###### Function:\n`callMethod`", + "description": "###### What this is:\n*The ten most-called RPC commands over the recent window.*\n\n###### How it's computed:\n*Total invocation counts per command over the last 5 minutes, ranked and capped at ten.*\n\n###### Reading it:\n*The top bars are the hottest endpoints driving node load.*\n\n###### Healthy range:\n*Workload-dependent; typically led by lightweight status queries.*\n\n###### Watch for:\n*An unexpected command dominating the ranking, consistent with automated abuse.*\n\n###### Keywords:\n- **RPC command / method** *(per node)* \u2014 a named API request served by the node (e.g. account_info, ledger, submit), the unit RPC panels break down by.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[RPCHandler.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/detail/RPCHandler.cpp)\n\n###### Function:\n`callMethod`\n\n###### References:\n[RPC command / method](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#rpc-command-method)", "type": "bargauge", "gridPos": { "h": 8, @@ -305,14 +343,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(topk(10, sum by (command, service_instance_id, xrpl_branch, xrpl_node_role) (increase(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", command=~\"$command\", span_name=~\"rpc.command.*\"}[$__rate_interval]))), \"series\", \"$1\", \"command\", \"(.*)\")" + "expr": "label_replace(label_join(label_replace(topk(10, sum by (command, service_instance_id, xrpl_branch, xrpl_node_role) (increase(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", command=~\"$command\", span_name=~\"rpc.command.*\"}[$__rate_interval]))), \"series\", \"$1\", \"command\", \"(.*)\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "short" }, "overrides": [] @@ -321,7 +360,7 @@ }, { "title": "WebSocket Message Rate", - "description": "###### What this is:\n*How many RPC messages arrive over WebSocket connections per second.*\n\n###### How it's computed:\n*Per-second rate of processed WebSocket RPC messages over 5 minutes, per node.*\n\n###### Reading it:\n*Non-zero only when clients use WebSocket; zero is normal for HTTP-only nodes.*\n\n###### Healthy range:\n*Workload-dependent; zero if no WebSocket clients.*\n\n###### Watch for:\n*A sudden surge from a single source, consistent with a chatty or abusive subscription client.*\n\n###### Source:\n[ServerHandler.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/detail/ServerHandler.cpp)\n\n###### Function:\n`ServerHandler::processSession`", + "description": "###### What this is:\n*How many RPC messages arrive over WebSocket connections per second.*\n\n###### How it's computed:\n*Per-second rate of processed WebSocket RPC messages over 5 minutes, per node.*\n\n###### Reading it:\n*Non-zero only when clients use WebSocket; zero is normal for HTTP-only nodes.*\n\n###### Healthy range:\n*Workload-dependent; zero if no WebSocket clients.*\n\n###### Watch for:\n*A sudden surge from a single source, consistent with a chatty or abusive subscription client.*\n\n###### Keywords:\n- **RPC command / method** *(per node)* \u2014 a named API request served by the node (e.g. account_info, ledger, submit), the unit RPC panels break down by.\n- **WebSocket** *(per node)* \u2014 a persistent-connection API transport used by subscription clients; zero on HTTP-only nodes.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[ServerHandler.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/detail/ServerHandler.cpp)\n\n###### Function:\n`ServerHandler::processSession`\n\n###### References:\n[RPC command / method](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods) \u00b7 [WebSocket](https://xrpl.org/docs/references/http-websocket-apis/api-conventions) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#rpc-command-method)", "type": "stat", "gridPos": { "h": 8, @@ -339,14 +378,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", command=~\"$command\", span_name=\"rpc.ws_message\"}[$__rate_interval])), \"series\", \"WS Messages / Sec\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", command=~\"$command\", span_name=\"rpc.ws_message\"}[$__rate_interval])), \"series\", \"WS Messages / Sec\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: messages/s" }, "overrides": [] @@ -355,11 +395,11 @@ }, { "title": "RPC Resource Cost by Command", - "description": "###### What this is:\n*RPC traffic grouped by resource-cost category rather than by command name.*\n\n###### How it's computed:\n*Per-second rate of commands over 5 minutes, grouped by load/cost category.*\n\n###### Reading it:\n*Cheap categories should dominate; costly ones should be a small slice.*\n\n###### Healthy range:\n*Workload-dependent; low-cost categories carry most traffic.*\n\n###### Watch for:\n*Growth in high-cost categories such as exception or malformed requests, which point to problematic clients.*\n\n###### Source:\n[RPCHandler.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/detail/RPCHandler.cpp)\n\n###### Function:\n`callMethod`", + "description": "###### What this is:\n*RPC traffic grouped by resource-cost category rather than by command name.*\n\n###### How it's computed:\n*Per-second rate of commands over 5 minutes, grouped by load/cost category.*\n\n###### Reading it:\n*Cheap categories should dominate; costly ones should be a small slice.*\n\n###### Healthy range:\n*Workload-dependent; low-cost categories carry most traffic.*\n\n###### Watch for:\n*Growth in high-cost categories such as exception or malformed requests, which point to problematic clients.*\n\n###### Keywords:\n- **RPC command / method** *(per node)* \u2014 a named API request served by the node (e.g. account_info, ledger, submit), the unit RPC panels break down by.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[RPCHandler.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/detail/RPCHandler.cpp)\n\n###### Function:\n`callMethod`\n\n###### References:\n[RPC command / method](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#rpc-command-method)", "type": "timeseries", "gridPos": { "h": 8, - "w": 24, + "w": 12, "x": 0, "y": 33 }, @@ -378,14 +418,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (load_type, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=~\"rpc.command.*\", load_type!=\"\"}[$__rate_interval])), \"series\", \"$1\", \"load_type\", \"(.*)\")" + "expr": "label_replace(label_join(label_replace(sum by (service_instance_id, load_type, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=~\"rpc.command.*\", load_type!=\"\"}[$__rate_interval])), \"series\", \"$1\", \"load_type\", \"(.*)\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: requests/s", "custom": { "axisLabel": "Requests / Sec", @@ -401,13 +442,13 @@ }, { "title": "Batch vs Single RPC Requests", - "description": "###### What this is:\n*The rate of batched RPC requests versus single-command requests.*\n\n###### How it's computed:\n*Per-second rates of batch and single requests over 5 minutes, per node.*\n\n###### Reading it:\n*Single requests usually dominate; batch is used by bulk clients.*\n\n###### Healthy range:\n*Workload-dependent; batch typically a minority.*\n\n###### Watch for:\n*A batch rate climbing sharply, consistent with bulk automation or amplification attempts.*\n\n###### Source:\n[ServerHandler.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/detail/ServerHandler.cpp)\n\n###### Function:\n`ServerHandler::processRequest`", + "description": "###### What this is:\n*The rate of batched RPC requests versus single-command requests.*\n\n###### How it's computed:\n*Per-second rates of batch and single requests over 5 minutes, per node.*\n\n###### Reading it:\n*Single requests usually dominate; batch is used by bulk clients.*\n\n###### Healthy range:\n*Workload-dependent; batch typically a minority.*\n\n###### Watch for:\n*A batch rate climbing sharply, consistent with bulk automation or amplification attempts.*\n\n###### Keywords:\n- **RPC command / method** *(per node)* \u2014 a named API request served by the node (e.g. account_info, ledger, submit), the unit RPC panels break down by.\n- **Batch vs single RPC** *(per node)* \u2014 whether an RPC request carried several commands (batch) or one (single); batch is used by bulk clients.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[ServerHandler.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/rpc/detail/ServerHandler.cpp)\n\n###### Function:\n`ServerHandler::processRequest`\n\n###### References:\n[RPC command / method](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#rpc-command-method)", "type": "timeseries", "gridPos": { "h": 8, "w": 12, - "x": 0, - "y": 41 + "x": 12, + "y": 33 }, "options": { "tooltip": { @@ -419,20 +460,22 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"rpc.process\", is_batch=\"true\"}[$__rate_interval])), \"series\", \"Batch\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"rpc.process\", is_batch=\"true\"}[$__rate_interval])), \"series\", \"Batch\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"rpc.process\", is_batch=\"false\"}[$__rate_interval])), \"series\", \"Single\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"rpc.process\", is_batch=\"false\"}[$__rate_interval])), \"series\", \"Single\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: requests/s", "custom": { "axisLabel": "Requests / Sec", @@ -455,19 +498,19 @@ "h": 1, "w": 24, "x": 0, - "y": 49 + "y": 41 }, "panels": [] }, { "title": "RPC Call Rate (All Methods)", - "description": "###### What this is:\n*Overall rate of RPC method calls that started, finished, and errored, across all methods.*\n\n###### How it's computed:\n*Per-second rate of each counter over a 5-minute window, summed per node.*\n\n###### Reading it:\n*Started should closely track finished; errored should be a small fraction.*\n\n###### Healthy range:\n*Workload-dependent; started \u2248 finished, errored near zero.*\n\n###### Watch for:\n*A growing gap between started and finished (calls hanging), or an errored line that rises with load.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`recordRpcStarted / recordRpcFinished / recordRpcErrored`", + "description": "###### What this is:\n*Overall rate of RPC method calls that started, finished, and errored, across all methods.*\n\n###### How it's computed:\n*Per-second rate of each counter over a 5-minute window, summed per node.*\n\n###### Reading it:\n*Started should closely track finished; errored should be a small fraction.*\n\n###### Healthy range:\n*Workload-dependent; started \u2248 finished, errored near zero.*\n\n###### Watch for:\n*A growing gap between started and finished (calls hanging), or an errored line that rises with load.*\n\n###### Keywords:\n- **RPC command / method** *(per node)* \u2014 a named API request served by the node (e.g. account_info, ledger, submit), the unit RPC panels break down by.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`recordRpcStarted / recordRpcFinished / recordRpcErrored`\n\n###### References:\n[RPC command / method](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#rpc-command-method)", "type": "timeseries", "gridPos": { "h": 8, "w": 24, "x": 0, - "y": 50 + "y": 42 }, "options": { "tooltip": { @@ -479,26 +522,29 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(rpc_method_started_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", method=~\"$method\"}[$__rate_interval])), \"series\", \"Started/s\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(rpc_method_started_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", method=~\"$method\"}[$__rate_interval])), \"series\", \"Started/s\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(rpc_method_finished_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", method=~\"$method\"}[$__rate_interval])), \"series\", \"Finished/s\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(rpc_method_finished_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", method=~\"$method\"}[$__rate_interval])), \"series\", \"Finished/s\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(rpc_method_errored_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", method=~\"$method\"}[$__rate_interval])), \"series\", \"Errored/s\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(rpc_method_errored_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", method=~\"$method\"}[$__rate_interval])), \"series\", \"Errored/s\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: calls/s", "custom": { "drawStyle": "line", @@ -520,7 +566,60 @@ }, { "title": "Per-Method Call Rate (Top 10)", - "description": "###### What this is:\n*The ten busiest RPC methods by call rate.*\n\n###### How it's computed:\n*Per-second start rate over 5 minutes, per method, showing the top ten.*\n\n###### Reading it:\n*Identifies which methods dominate load; the mix shifts with client behaviour.*\n\n###### Healthy range:\n*Workload-dependent.*\n\n###### Watch for:\n*A single method suddenly dominating, which can signal a runaway client or abusive query pattern.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`recordRpcStarted`", + "description": "###### What this is:\n*The ten busiest RPC methods by call rate.*\n\n###### How it's computed:\n*Per-second start rate over 5 minutes, per method, showing the top ten.*\n\n###### Reading it:\n*Identifies which methods dominate load; the mix shifts with client behaviour.*\n\n###### Healthy range:\n*Workload-dependent.*\n\n###### Watch for:\n*A single method suddenly dominating, which can signal a runaway client or abusive query pattern.*\n\n###### Keywords:\n- **RPC command / method** *(per node)* \u2014 a named API request served by the node (e.g. account_info, ledger, submit), the unit RPC panels break down by.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`recordRpcStarted`\n\n###### References:\n[RPC command / method](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#rpc-command-method)", + "type": "timeseries", + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 50 + }, + "options": { + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + }, + "legend": { + "displayMode": "table", + "placement": "right", + "calcs": ["mean", "max"] + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(topk(10, rate(rpc_method_started_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", method=~\"$method\"}[$__rate_interval])), \"series\", \"$1\", \"method\", \"(.*)\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" + } + ], + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "unit": "suffix: calls/s", + "custom": { + "drawStyle": "line", + "lineWidth": 1, + "fillOpacity": 5, + "axisLabel": "Calls / Sec", + "spanNulls": 1800000, + "insertNulls": false, + "showPoints": "auto", + "pointSize": 3 + }, + "color": { + "mode": "palette-classic" + } + }, + "overrides": [] + }, + "id": 14 + }, + { + "title": "Per-Method Error Rate (Top 10)", + "description": "###### What this is:\n*The ten RPC methods producing the most errors.*\n\n###### How it's computed:\n*Per-second error rate over 5 minutes, per method, top ten.*\n\n###### Reading it:\n*Normally near zero; persistent errors point to a specific failing method.*\n\n###### Healthy range:\n*Near zero for well-behaved traffic.*\n\n###### Watch for:\n*Sustained errors concentrated on one method \u2014 a broken client, a bad input, or probing.*\n\n###### Keywords:\n- **RPC command / method** *(per node)* \u2014 a named API request served by the node (e.g. account_info, ledger, submit), the unit RPC panels break down by.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`recordRpcErrored`\n\n###### References:\n[RPC command / method](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#rpc-command-method)", "type": "timeseries", "gridPos": { "h": 8, @@ -543,66 +642,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(topk(10, rate(rpc_method_started_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", method=~\"$method\"}[$__rate_interval])), \"series\", \"$1\", \"method\", \"(.*)\")" + "expr": "label_replace(label_join(label_replace(topk(10, rate(rpc_method_errored_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", method=~\"$method\"}[$__rate_interval])), \"series\", \"$1\", \"method\", \"(.*)\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "suffix: calls/s", - "custom": { - "drawStyle": "line", - "lineWidth": 1, - "fillOpacity": 5, - "axisLabel": "Calls / Sec", - "spanNulls": 1800000, - "insertNulls": false, - "showPoints": "auto", - "pointSize": 3 - }, - "color": { - "mode": "palette-classic" - } - }, - "overrides": [] - }, - "id": 14 - }, - { - "title": "Per-Method Error Rate (Top 10)", - "description": "###### What this is:\n*The ten RPC methods producing the most errors.*\n\n###### How it's computed:\n*Per-second error rate over 5 minutes, per method, top ten.*\n\n###### Reading it:\n*Normally near zero; persistent errors point to a specific failing method.*\n\n###### Healthy range:\n*Near zero for well-behaved traffic.*\n\n###### Watch for:\n*Sustained errors concentrated on one method \u2014 a broken client, a bad input, or probing.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`recordRpcErrored`", - "type": "timeseries", - "gridPos": { - "h": 8, - "w": 24, - "x": 0, - "y": 66 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - }, - "legend": { - "displayMode": "table", - "placement": "right", - "calcs": ["mean", "max"] - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(topk(10, rate(rpc_method_errored_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", method=~\"$method\"}[$__rate_interval])), \"series\", \"$1\", \"method\", \"(.*)\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: errors/s", "custom": { "drawStyle": "line", @@ -624,13 +672,13 @@ }, { "title": "RPC Latency - All Methods", - "description": "###### What this is:\n*Aggregate RPC handler latency across all methods (p75 and p99).*\n\n###### How it's computed:\n*Percentiles of the method-duration histogram over a 5-minute window.*\n\n###### Reading it:\n*p75 reflects typical responsiveness; p99 captures the slow tail.*\n\n###### Healthy range:\n*Sub-millisecond to low-millisecond for light commands; heavier commands run longer.*\n\n###### Watch for:\n*A rising p99 while p75 stays flat \u2014 a subset of calls degrading, often from expensive queries.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`recordRpcFinished`", + "description": "###### What this is:\n*Aggregate RPC handler latency across all methods (p75 and p99).*\n\n###### How it's computed:\n*Percentiles of the method-duration histogram over a 5-minute window.*\n\n###### Reading it:\n*p75 reflects typical responsiveness; p99 captures the slow tail.*\n\n###### Healthy range:\n*Sub-millisecond to low-millisecond for light commands; heavier commands run longer.*\n\n###### Watch for:\n*A rising p99 while p75 stays flat \u2014 a subset of calls degrading, often from expensive queries.*\n\n###### Keywords:\n- **RPC command / method** *(per node)* \u2014 a named API request served by the node (e.g. account_info, ledger, submit), the unit RPC panels break down by.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`recordRpcFinished`\n\n###### References:\n[RPC command / method](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#rpc-command-method)", "type": "timeseries", "gridPos": { "h": 8, "w": 12, "x": 0, - "y": 74 + "y": 66 }, "options": { "tooltip": { @@ -642,20 +690,22 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(histogram_quantile(0.75, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(rpc_method_us_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", method=~\"$method\"}[5m]))), \"series\", \"p75\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(histogram_quantile(0.75, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(rpc_method_us_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", method=~\"$method\"}[5m]))), \"series\", \"p75\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(histogram_quantile(0.99, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(rpc_method_us_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", method=~\"$method\"}[5m]))), \"series\", \"p99\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(histogram_quantile(0.99, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(rpc_method_us_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", method=~\"$method\"}[5m]))), \"series\", \"p99\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "\u00b5s", "custom": { "drawStyle": "line", @@ -677,7 +727,60 @@ }, { "title": "Per-Method Latency (p99, Top 10 Slowest)", - "description": "###### What this is:\n*The ten slowest RPC methods by tail latency.*\n\n###### How it's computed:\n*p99 of each method's duration histogram over 5 minutes, top ten.*\n\n###### Reading it:\n*Surfaces which specific methods are expensive.*\n\n###### Healthy range:\n*Method-dependent; ledger/account queries are heavier than status calls.*\n\n###### Watch for:\n*A method whose p99 climbs over time, or an unexpectedly cheap method appearing here.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`recordRpcFinished`", + "description": "###### What this is:\n*The ten slowest RPC methods by tail latency.*\n\n###### How it's computed:\n*p99 of each method's duration histogram over 5 minutes, top ten.*\n\n###### Reading it:\n*Surfaces which specific methods are expensive.*\n\n###### Healthy range:\n*Method-dependent; ledger/account queries are heavier than status calls.*\n\n###### Watch for:\n*A method whose p99 climbs over time, or an unexpectedly cheap method appearing here.*\n\n###### Keywords:\n- **RPC command / method** *(per node)* \u2014 a named API request served by the node (e.g. account_info, ledger, submit), the unit RPC panels break down by.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`recordRpcFinished`\n\n###### References:\n[RPC command / method](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#rpc-command-method)", + "type": "timeseries", + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 74 + }, + "options": { + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + }, + "legend": { + "displayMode": "table", + "placement": "right", + "calcs": ["mean", "max"] + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(topk(10, histogram_quantile(0.99, sum by (le, method, service_instance_id, xrpl_branch, xrpl_node_role) (rate(rpc_method_us_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", method=~\"$method\"}[5m])))), \"series\", \"$1\", \"method\", \"(.*)\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" + } + ], + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "unit": "\u00b5s", + "custom": { + "drawStyle": "line", + "lineWidth": 1, + "fillOpacity": 5, + "axisLabel": "Duration (\u03bcs)", + "spanNulls": 1800000, + "insertNulls": false, + "showPoints": "auto", + "pointSize": 3 + }, + "color": { + "mode": "palette-classic" + } + }, + "overrides": [] + }, + "id": 17 + }, + { + "title": "RPC Error Ratio by Method", + "description": "###### What this is:\n*The methods with the highest error rates, for spotting failure hotspots.*\n\n###### How it's computed:\n*Per-second error rate over 5 minutes, per method, top ten.*\n\n###### Reading it:\n*Highlights where failures concentrate.*\n\n###### Healthy range:\n*Near zero.*\n\n###### Watch for:\n*One method with a persistently high error rate \u2014 malformed requests or targeted probing.*\n\n###### Keywords:\n- **RPC command / method** *(per node)* \u2014 a named API request served by the node (e.g. account_info, ledger, submit), the unit RPC panels break down by.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`recordRpcErrored`\n\n###### References:\n[RPC command / method](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#rpc-command-method)", "type": "timeseries", "gridPos": { "h": 8, @@ -700,66 +803,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(topk(10, histogram_quantile(0.99, sum by (le, method, service_instance_id, xrpl_branch, xrpl_node_role) (rate(rpc_method_us_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", method=~\"$method\"}[5m])))), \"series\", \"$1\", \"method\", \"(.*)\")" + "expr": "label_replace(label_join(label_replace(topk(10, rate(rpc_method_errored_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", method=~\"$method\"}[$__rate_interval]) / (rate(rpc_method_started_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", method=~\"$method\"}[$__rate_interval]) > 0)), \"series\", \"$1\", \"method\", \"(.*)\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "\u00b5s", - "custom": { - "drawStyle": "line", - "lineWidth": 1, - "fillOpacity": 5, - "axisLabel": "Duration (\u03bcs)", - "spanNulls": 1800000, - "insertNulls": false, - "showPoints": "auto", - "pointSize": 3 - }, - "color": { - "mode": "palette-classic" - } - }, - "overrides": [] - }, - "id": 17 - }, - { - "title": "RPC Error Ratio by Method", - "description": "###### What this is:\n*The methods with the highest error rates, for spotting failure hotspots.*\n\n###### How it's computed:\n*Per-second error rate over 5 minutes, per method, top ten.*\n\n###### Reading it:\n*Highlights where failures concentrate.*\n\n###### Healthy range:\n*Near zero.*\n\n###### Watch for:\n*One method with a persistently high error rate \u2014 malformed requests or targeted probing.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`recordRpcErrored`", - "type": "timeseries", - "gridPos": { - "h": 8, - "w": 24, - "x": 0, - "y": 90 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - }, - "legend": { - "displayMode": "table", - "placement": "right", - "calcs": ["mean", "max"] - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(topk(10, rate(rpc_method_errored_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", method=~\"$method\"}[$__rate_interval]) / (rate(rpc_method_started_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", method=~\"$method\"}[$__rate_interval]) > 0)), \"series\", \"$1\", \"method\", \"(.*)\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "percentunit", "min": 0, "max": 1, @@ -799,13 +851,13 @@ }, { "title": "Current RPC Latency (p99 Gauge)", - "description": "###### What this is:\n*Current tail latency (p99) of RPC handling across all methods, as a live gauge.*\n\n###### How it's computed:\n*p99 of the method-duration histogram over the recent window.*\n\n###### Reading it:\n*A single at-a-glance number for current RPC responsiveness.*\n\n###### Healthy range:\n*Low-millisecond under normal load.*\n\n###### Watch for:\n*Sustained elevation, indicating the node is under query pressure.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`recordRpcFinished`", + "description": "###### What this is:\n*Current tail latency (p99) of RPC handling across all methods, as a live gauge.*\n\n###### How it's computed:\n*p99 of the method-duration histogram over the recent window.*\n\n###### Reading it:\n*A single at-a-glance number for current RPC responsiveness.*\n\n###### Healthy range:\n*Low-millisecond under normal load.*\n\n###### Watch for:\n*Sustained elevation, indicating the node is under query pressure.*\n\n###### Keywords:\n- **RPC command / method** *(per node)* \u2014 a named API request served by the node (e.g. account_info, ledger, submit), the unit RPC panels break down by.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`recordRpcFinished`\n\n###### References:\n[RPC command / method](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#rpc-command-method)", "type": "gauge", "gridPos": { "h": 8, "w": 12, "x": 0, - "y": 98 + "y": 90 }, "options": { "reduceOptions": { @@ -815,19 +867,21 @@ }, "orientation": "auto", "showThresholdLabels": false, - "showThresholdMarkers": true + "showThresholdMarkers": true, + "tooltip": {} }, "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(histogram_quantile(0.99, sum by (le, xrpl_branch, xrpl_node_role) (rate(rpc_method_us_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", method=~\"$method\"}[5m]))), \"series\", \"p99 Latency\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(histogram_quantile(0.99, sum by (service_instance_id, le, xrpl_branch, xrpl_node_role) (rate(rpc_method_us_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", method=~\"$method\"}[5m]))), \"series\", \"p99 Latency\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "\u00b5s", "min": 0, "thresholds": { @@ -857,6 +911,19 @@ "tags": ["rpc"], "templating": { "list": [ + { + "name": "DS_PROMETHEUS", + "type": "datasource", + "label": "Prometheus", + "query": "prometheus", + "regex": "", + "current": {}, + "hide": 0, + "refresh": 1, + "includeAll": false, + "multi": false, + "options": [] + }, { "name": "service_name", "label": "Service Name", @@ -865,7 +932,7 @@ "query": "label_values(service_name)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -885,7 +952,7 @@ "query": "label_values(deployment_environment)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -905,7 +972,7 @@ "query": "label_values(xrpl_network_type)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -985,7 +1052,7 @@ "query": "label_values(target_info, service_instance_id)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -1005,7 +1072,7 @@ "query": "label_values(span_calls_total{span_name=~\"rpc.command.*\"}, command)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -1025,7 +1092,7 @@ "query": "label_values(rpc_method_started_total, method)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -1045,6 +1112,6 @@ }, "title": "RPC Performance", "uid": "rpc-performance", - "refresh": "5s", - "description": "What this shows: Per-command and per-method RPC performance: call rates, error rates, and latency distributions.\nUse it to: Identify slow or failing RPC commands and track client-facing request latency." + "refresh": "30s", + "description": "What this shows: Per-command and per-method RPC performance: call rates, error rates, and latency distributions. \u2014 Use it to: Identify slow or failing RPC commands and track client-facing request latency." } diff --git a/docker/telemetry/grafana/dashboards/transaction-overview.json b/docker/telemetry/grafana/dashboards/transaction-overview.json index e784b39015..ecb9450172 100644 --- a/docker/telemetry/grafana/dashboards/transaction-overview.json +++ b/docker/telemetry/grafana/dashboards/transaction-overview.json @@ -1,6 +1,36 @@ { "annotations": { - "list": [] + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + }, + { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": false, + "iconColor": "rgb(70, 70, 70)", + "name": "Annotate perf-iac runs", + "target": { + "limit": 100, + "matchAny": false, + "tags": ["perf-iac"], + "type": "tags" + }, + "type": "tags" + } + ] }, "editable": true, "fiscalYearStartMonth": 0, @@ -10,7 +40,7 @@ "panels": [ { "title": "Transaction Apply Failed Rate", - "description": "###### What this is:\n*Transactions that fail during the apply (transactor) stage, per second \u2014 i.e. the transactor ran but the result was not tesSUCCESS.*\n\n###### How it's computed:\n*Per-second rate of tx.transactor spans at the apply stage whose ter_result is not tesSUCCESS, over the dashboard rate interval, per node.*\n\n###### Reading it:\n*A steady low background is normal (tefPAST_SEQ, tecUNFUNDED, etc.); the transactor legitimately rejects many transactions.*\n\n###### Healthy range:\n*A low, stable rate; workload-dependent.*\n\n###### Watch for:\n*A sharp rise above baseline, which points to a submission pattern that is systematically failing at apply.*\n\n###### Source:\n[Transactor.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/tx/detail/Transactor.cpp)\n\n###### Function:\n`Transactor::operator()`", + "description": "###### What this is:\n*Transactions that fail during the apply (transactor) stage, per second \u2014 i.e. the transactor ran but the result was not tesSUCCESS.*\n\n###### How it's computed:\n*Per-second rate of tx.transactor spans at the apply stage whose ter_result is not tesSUCCESS, over the dashboard rate interval, per node.*\n\n###### Reading it:\n*A steady low background is normal (tefPAST_SEQ, tecUNFUNDED, etc.); the transactor legitimately rejects many transactions.*\n\n###### Healthy range:\n*A low, stable rate; workload-dependent.*\n\n###### Watch for:\n*A sharp rise above baseline, which points to a submission pattern that is systematically failing at apply.*\n\n###### Keywords:\n- **Transactor** *(per node)* \u2014 the rippled component that executes one transaction's type-specific logic against the ledger.\n- **Transaction result codes** *(network-wide)* \u2014 the outcome code a transaction returns \u2014 tesSUCCESS, or a tec/tef/tem/ter/tel class code on failure.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[Transactor.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/tx/detail/Transactor.cpp)\n\n###### Function:\n`Transactor::operator()`\n\n###### References:\n[Transactor](https://xrpl.org/docs/references/protocol/transactions/transaction-results) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#transactor)", "type": "stat", "gridPos": { "h": 4, @@ -28,14 +58,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"tx.transactor\", stage=\"apply\", ter_result!~\"tesSUCCESS|\"}[$__rate_interval])), \"series\", \"Failed / Sec\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"tx.transactor\", stage=\"apply\", ter_result!~\"tesSUCCESS|\"}[$__rate_interval])), \"series\", \"Failed / Sec\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: transactions/s", "thresholds": { "steps": [ @@ -60,7 +91,7 @@ }, { "title": "Transaction Processing Latency by Type", - "description": "###### What this is:\n*The 95th-percentile time to process a transaction, broken down by transaction type.*\n\n###### How it's computed:\n*95th-percentile of processing durations over 5 minutes, grouped by transaction type and node.*\n\n###### Reading it:\n*Lower is better; compare types to find the expensive ones.*\n\n###### Healthy range:\n*A few milliseconds for simple payments; workload- and type-dependent.*\n\n###### Watch for:\n*A type whose latency climbs, pointing to expensive processing or resource pressure.*\n\n###### Source:\n[NetworkOPs.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/NetworkOPs.cpp)\n\n###### Function:\n`NetworkOPsImp::processTransaction`", + "description": "###### What this is:\n*The 95th-percentile time to process a transaction, broken down by transaction type.*\n\n###### How it's computed:\n*95th-percentile of processing durations over 5 minutes, grouped by transaction type and node.*\n\n###### Reading it:\n*Lower is better; compare types to find the expensive ones.*\n\n###### Healthy range:\n*A few milliseconds for simple payments; workload- and type-dependent.*\n\n###### Watch for:\n*A type whose latency climbs, pointing to expensive processing or resource pressure.*\n\n###### Keywords:\n- **Transaction type** *(network-wide)* \u2014 the kind of transaction (Payment, OfferCreate, TrustSet, AMM*, NFToken*, etc.), used as a breakdown dimension.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[NetworkOPs.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/NetworkOPs.cpp)\n\n###### Function:\n`NetworkOPsImp::processTransaction`\n\n###### References:\n[Transaction type](https://xrpl.org/docs/references/protocol/transactions/types) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#transaction-type)", "type": "timeseries", "gridPos": { "h": 8, @@ -83,14 +114,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(histogram_quantile(0.95, sum by (le, tx_type, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_duration_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"tx.process\", tx_type=~\"$tx_type\"}[5m]))), \"series\", \"$1\", \"tx_type\", \"(.*)\")" + "expr": "label_replace(label_join(label_replace(histogram_quantile(0.95, sum by (le, tx_type, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_duration_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"tx.process\", tx_type=~\"$tx_type\"}[5m]))), \"series\", \"$1\", \"tx_type\", \"(.*)\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "ms", "custom": { "axisLabel": "Latency (ms)", @@ -106,7 +138,7 @@ }, { "title": "Transaction Rate by Type", - "description": "###### What this is:\n*How many transactions of each type enter processing per second.*\n\n###### How it's computed:\n*Per-second processing rate over 5 minutes, grouped by transaction type and node.*\n\n###### Reading it:\n*Shows the transaction mix; payments usually lead.*\n\n###### Healthy range:\n*Workload-dependent; mix reflects network activity.*\n\n###### Watch for:\n*A single type spiking far above baseline, consistent with a spam campaign of that type.*\n\n###### Source:\n[NetworkOPs.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/NetworkOPs.cpp)\n\n###### Function:\n`NetworkOPsImp::processTransaction`", + "description": "###### What this is:\n*How many transactions of each type enter processing per second.*\n\n###### How it's computed:\n*Per-second processing rate over 5 minutes, grouped by transaction type and node.*\n\n###### Reading it:\n*Shows the transaction mix; payments usually lead.*\n\n###### Healthy range:\n*Workload-dependent; mix reflects network activity.*\n\n###### Watch for:\n*A single type spiking far above baseline, consistent with a spam campaign of that type.*\n\n###### Keywords:\n- **Transaction type** *(network-wide)* \u2014 the kind of transaction (Payment, OfferCreate, TrustSet, AMM*, NFToken*, etc.), used as a breakdown dimension.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[NetworkOPs.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/NetworkOPs.cpp)\n\n###### Function:\n`NetworkOPsImp::processTransaction`\n\n###### References:\n[Transaction type](https://xrpl.org/docs/references/protocol/transactions/types) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#transaction-type)", "type": "timeseries", "gridPos": { "h": 8, @@ -129,14 +161,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (tx_type, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"tx.process\", tx_type=~\"$tx_type\"}[$__rate_interval])), \"series\", \"$1\", \"tx_type\", \"(.*)\")" + "expr": "label_replace(label_join(label_replace(sum by (tx_type, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"tx.process\", tx_type=~\"$tx_type\"}[$__rate_interval])), \"series\", \"$1\", \"tx_type\", \"(.*)\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: transactions/s", "custom": { "axisLabel": "Transactions / Sec", @@ -152,7 +185,7 @@ }, { "title": "Transaction Results by Type", - "description": "###### What this is:\n*The non-success result codes transactions return, by transaction type.*\n\n###### How it's computed:\n*Per-second rate of failing transactions over 5 minutes, grouped by type and result code.*\n\n###### Reading it:\n*Fewer failures is better; use it to see which types fail and why.*\n\n###### Healthy range:\n*Workload-dependent; a modest background of expected failures is normal.*\n\n###### Watch for:\n*A surge of one failure code for one type, indicating a systemic issue or abusive submissions.*\n\n###### Source:\n[NetworkOPs.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/NetworkOPs.cpp)\n\n###### Function:\n`NetworkOPsImp::processTransaction`", + "description": "###### What this is:\n*The non-success result codes transactions return, by transaction type.*\n\n###### How it's computed:\n*Per-second rate of failing transactions over 5 minutes, grouped by type and result code.*\n\n###### Reading it:\n*Fewer failures is better; use it to see which types fail and why.*\n\n###### Healthy range:\n*Workload-dependent; a modest background of expected failures is normal.*\n\n###### Watch for:\n*A surge of one failure code for one type, indicating a systemic issue or abusive submissions.*\n\n###### Keywords:\n- **Transaction result codes** *(network-wide)* \u2014 the outcome code a transaction returns \u2014 tesSUCCESS, or a tec/tef/tem/ter/tel class code on failure.\n- **Transaction type** *(network-wide)* \u2014 the kind of transaction (Payment, OfferCreate, TrustSet, AMM*, NFToken*, etc.), used as a breakdown dimension.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[NetworkOPs.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/NetworkOPs.cpp)\n\n###### Function:\n`NetworkOPsImp::processTransaction`\n\n###### References:\n[Transaction result codes](https://xrpl.org/docs/references/protocol/transactions/transaction-results) \u00b7 [Transaction type](https://xrpl.org/docs/references/protocol/transactions/types) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#transaction-result-codes)", "type": "timeseries", "gridPos": { "h": 8, @@ -175,10 +208,10 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "sum by (tx_type, ter_result, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"tx.process\", tx_type=~\"$tx_type\", ter_result=~\"$ter_result\", ter_result!=\"tesSUCCESS\"}[$__rate_interval]))", - "legendFormat": "{{tx_type}} [{{ter_result}}, {{service_instance_id}}] [{{xrpl_branch}}, {{xrpl_node_role}}]" + "expr": "label_replace(label_join(label_replace(sum by (tx_type, ter_result, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"tx.process\", tx_type=~\"$tx_type\", ter_result=~\"$ter_result\", ter_result!=\"tesSUCCESS\"}[$__rate_interval])), \"series\", \"$1\", \"tx_type\", \"(.*)\"), \"xrpl_ident\", \", \", \"ter_result\", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { @@ -190,7 +223,8 @@ "insertNulls": false, "showPoints": "auto", "pointSize": 3 - } + }, + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}" }, "overrides": [] }, @@ -198,7 +232,7 @@ }, { "title": "Transaction Receive vs Suppressed", - "description": "###### What this is:\n*Raw transactions received from peers, split by whether they were duplicates suppressed before processing.*\n\n###### How it's computed:\n*Per-second rate of received transactions over the window, split by suppressed flag and node.*\n\n###### Reading it:\n*A large suppressed share is normal, since the same transaction arrives from many peers.*\n\n###### Healthy range:\n*Workload-dependent; suppressed typically exceeds newly processed.*\n\n###### Watch for:\n*A collapse in suppression (duplicate filtering failing) or an overall receive flood.*\n\n###### Source:\n[PeerImp.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/PeerImp.cpp)\n\n###### Function:\n`PeerImp::handleTransaction`", + "description": "###### What this is:\n*Raw transactions received from peers, split by whether they were duplicates suppressed before processing.*\n\n###### How it's computed:\n*Per-second rate of received transactions over the window, split by suppressed flag and node.*\n\n###### Reading it:\n*A large suppressed share is normal, since the same transaction arrives from many peers.*\n\n###### Healthy range:\n*Workload-dependent; suppressed typically exceeds newly processed.*\n\n###### Watch for:\n*A collapse in suppression (duplicate filtering failing) or an overall receive flood.*\n\n###### Keywords:\n- **Transaction suppression** *(per node)* \u2014 dropping a transaction already seen from another peer, so it is not reprocessed.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[PeerImp.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/PeerImp.cpp)\n\n###### Function:\n`PeerImp::handleTransaction`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#transaction-suppression)", "type": "timeseries", "gridPos": { "h": 8, @@ -216,14 +250,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (suppressed, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{span_name=\"tx.receive\", tx_type=~\"$tx_type\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])), \"series\", \"Suppressed $1\", \"suppressed\", \"(.*)\")" + "expr": "label_replace(label_join(label_replace(sum by (suppressed, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{span_name=\"tx.receive\", tx_type=~\"$tx_type\", service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])), \"series\", \"Suppressed $1\", \"suppressed\", \"(.*)\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: transactions/s", "custom": { "axisLabel": "Transactions / Sec", @@ -239,7 +274,7 @@ }, { "title": "Transaction Processing Rate", - "description": "###### What this is:\n*Transactions entering the processing pipeline versus raw transactions arriving from peers.*\n\n###### How it's computed:\n*Per-second rates of processed transactions and received peer transactions over 5 minutes, per node.*\n\n###### Reading it:\n*Received sits above processed; the difference is deduplicated relay traffic.*\n\n###### Healthy range:\n*Workload-dependent; both scale with network volume.*\n\n###### Watch for:\n*A receive rate spiking well above processing, consistent with relay flooding.*\n\n###### Source:\n[NetworkOPs.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/NetworkOPs.cpp) \u00b7 [PeerImp.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/PeerImp.cpp)\n\n###### Function:\n`NetworkOPsImp::processTransaction ; PeerImp::handleTransaction`", + "description": "###### What this is:\n*Transactions entering the processing pipeline versus raw transactions arriving from peers.*\n\n###### How it's computed:\n*Per-second rates of processed transactions and received peer transactions over 5 minutes, per node.*\n\n###### Reading it:\n*Received sits above processed; the difference is deduplicated relay traffic.*\n\n###### Healthy range:\n*Workload-dependent; both scale with network volume.*\n\n###### Watch for:\n*A receive rate spiking well above processing, consistent with relay flooding.*\n\n###### Keywords:\n- **Transaction suppression** *(per node)* \u2014 dropping a transaction already seen from another peer, so it is not reprocessed.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[NetworkOPs.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/NetworkOPs.cpp) \u00b7 [PeerImp.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/overlay/detail/PeerImp.cpp)\n\n###### Function:\n`NetworkOPsImp::processTransaction ; PeerImp::handleTransaction`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#transaction-suppression)", "type": "timeseries", "gridPos": { "h": 8, @@ -257,20 +292,22 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"tx.process\", tx_type=~\"$tx_type\"}[$__rate_interval])), \"series\", \"tx.process / Sec\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"tx.process\", tx_type=~\"$tx_type\"}[$__rate_interval])), \"series\", \"tx.process / Sec\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"tx.receive\", tx_type=~\"$tx_type\"}[$__rate_interval])), \"series\", \"tx.receive / Sec\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"tx.receive\", tx_type=~\"$tx_type\"}[$__rate_interval])), \"series\", \"tx.receive / Sec\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: transactions/s", "custom": { "axisLabel": "Transactions / Sec", @@ -286,7 +323,7 @@ }, { "title": "Transaction Path Distribution", - "description": "###### What this is:\n*The split of transactions by origin: submitted locally versus relayed from peers.*\n\n###### How it's computed:\n*Processed-transaction rate over 5 minutes, split by local-origin flag and shown as proportions.*\n\n###### Reading it:\n*Most traffic on a network node is peer-relayed; local dominates on a submission node.*\n\n###### Healthy range:\n*Workload-dependent on the node's role.*\n\n###### Watch for:\n*An unexpected surge in local submissions, consistent with a local client flooding the node.*\n\n###### Source:\n[NetworkOPs.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/NetworkOPs.cpp)\n\n###### Function:\n`NetworkOPsImp::processTransaction`", + "description": "###### What this is:\n*The split of transactions by origin: submitted locally versus relayed from peers.*\n\n###### How it's computed:\n*Processed-transaction rate over 5 minutes, split by local-origin flag and shown as proportions.*\n\n###### Reading it:\n*Most traffic on a network node is peer-relayed; local dominates on a submission node.*\n\n###### Healthy range:\n*Workload-dependent on the node's role.*\n\n###### Watch for:\n*An unexpected surge in local submissions, consistent with a local client flooding the node.*\n\n###### Keywords:\n- **Local vs relayed transactions** *(per node)* \u2014 the origin split \u2014 transactions submitted directly to this node versus relayed from peers.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[NetworkOPs.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/NetworkOPs.cpp)\n\n###### Function:\n`NetworkOPsImp::processTransaction`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#local-vs-relayed-transactions)", "type": "piechart", "gridPos": { "h": 8, @@ -296,7 +333,7 @@ }, "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "short" }, "overrides": [] @@ -311,16 +348,17 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (local, service_instance_id, xrpl_branch, xrpl_node_role) (increase(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", local=~\"$tx_origin\", span_name=\"tx.process\", tx_type=~\"$tx_type\"}[$__rate_interval])), \"series\", \"Local $1\", \"local\", \"(.*)\")" + "expr": "label_replace(label_join(label_replace(sum by (local, service_instance_id, xrpl_branch, xrpl_node_role) (increase(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", local=~\"$tx_origin\", span_name=\"tx.process\", tx_type=~\"$tx_type\"}[$__rate_interval])), \"series\", \"Local $1\", \"local\", \"(.*)\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "id": 7 }, { "title": "Transaction Processing Duration Heatmap", - "description": "###### What this is:\n*The full distribution of transaction processing times over the window.*\n\n###### How it's computed:\n*Counts of transactions in each latency band per 5-minute window, shown as color density.*\n\n###### Reading it:\n*A tight low band is healthy; spread bands mean uneven processing time.*\n\n###### Healthy range:\n*Most mass in the low-latency bands; workload-dependent.*\n\n###### Watch for:\n*A distinct slow cluster (multi-modal timing) hidden by percentile charts.*\n\n###### Source:\n[NetworkOPs.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/NetworkOPs.cpp)\n\n###### Function:\n`NetworkOPsImp::processTransaction`", + "description": "###### What this is:\n*The full distribution of transaction processing times over the window.*\n\n###### How it's computed:\n*Counts of transactions in each latency band per 5-minute window, shown as color density.*\n\n###### Reading it:\n*A tight low band is healthy; spread bands mean uneven processing time.*\n\n###### Healthy range:\n*Most mass in the low-latency bands; workload-dependent.*\n\n###### Watch for:\n*A distinct slow cluster (multi-modal timing) hidden by percentile charts.*\n\n###### Keywords:\n- **Transaction** *(network event)* \u2014 a signed instruction submitted to the network and applied when a ledger closes.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[NetworkOPs.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/NetworkOPs.cpp)\n\n###### Function:\n`NetworkOPsImp::processTransaction`\n\n###### References:\n[Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md)", "type": "heatmap", "gridPos": { "h": 8, @@ -342,7 +380,8 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, "expr": "sum(increase(span_duration_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"tx.process\", tx_type=~\"$tx_type\"}[5m])) by (le)", "legendFormat": "{{le}}", @@ -358,7 +397,7 @@ }, { "title": "Transactor Duration by Type (p95)", - "description": "###### What this is:\n*The 95th-percentile execution time of the core transactor step, by transaction type.*\n\n###### How it's computed:\n*95th-percentile of transactor durations over 5 minutes, grouped by type and node.*\n\n###### Reading it:\n*Lower is better; reveals which types are most expensive to execute.*\n\n###### Healthy range:\n*Sub-millisecond to a few ms for most types; workload-dependent.*\n\n###### Watch for:\n*A type whose execution time grows, indicating expensive logic or ledger contention.*\n\n###### Source:\n[Transactor.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/tx/Transactor.cpp)\n\n###### Function:\n`Transactor::operator()`", + "description": "###### What this is:\n*The 95th-percentile execution time of the core transactor step, by transaction type.*\n\n###### How it's computed:\n*95th-percentile of transactor durations over 5 minutes, grouped by type and node.*\n\n###### Reading it:\n*Lower is better; reveals which types are most expensive to execute.*\n\n###### Healthy range:\n*Sub-millisecond to a few ms for most types; workload-dependent.*\n\n###### Watch for:\n*A type whose execution time grows, indicating expensive logic or ledger contention.*\n\n###### Keywords:\n- **Transactor** *(per node)* \u2014 the rippled component that executes one transaction's type-specific logic against the ledger.\n- **Transaction type** *(network-wide)* \u2014 the kind of transaction (Payment, OfferCreate, TrustSet, AMM*, NFToken*, etc.), used as a breakdown dimension.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[Transactor.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/tx/Transactor.cpp)\n\n###### Function:\n`Transactor::operator()`\n\n###### References:\n[Transactor](https://xrpl.org/docs/references/protocol/transactions/transaction-results) \u00b7 [Transaction type](https://xrpl.org/docs/references/protocol/transactions/types) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#transactor)", "type": "timeseries", "gridPos": { "h": 8, @@ -381,14 +420,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(histogram_quantile(0.95, sum by (le, tx_type, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_duration_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"tx.transactor\", tx_type=~\"$tx_type\"}[5m]))), \"series\", \"$1\", \"tx_type\", \"(.*)\")" + "expr": "label_replace(label_join(label_replace(histogram_quantile(0.95, sum by (le, tx_type, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_duration_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"tx.transactor\", tx_type=~\"$tx_type\"}[5m]))), \"series\", \"$1\", \"tx_type\", \"(.*)\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "ms", "custom": { "axisLabel": "Duration (ms)", @@ -404,7 +444,7 @@ }, { "title": "TxQ Accept: Applied Ratio per Node (State Timeline)", - "description": "###### What this is:\n*The applied fraction of TxQ accepts per node over time: the share of queued transactions that were included in a ledger versus removed on failure.*\n\n###### How it's computed:\n*Per node, applied accepts divided by applied-plus-failed accepts over the window.*\n\n###### Reading it:\n*Green (>=90% applied) is a healthy drain; yellow is degraded; red means accepts are mostly failing.*\n\n###### Healthy range:\n*At or near 100% applied when the queue is draining healthily; workload-dependent.*\n\n###### Watch for:\n*A node dropping into yellow or red, which signals queue pressure, under-bidding, or fee escalation on that node.*\n\n###### Source:\n[TxQ.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/detail/TxQ.cpp)\n\n###### Function:\n`TxQ::accept`", + "description": "###### What this is:\n*The applied fraction of TxQ accepts per node over time: the share of queued transactions that were included in a ledger versus removed on failure.*\n\n###### How it's computed:\n*Per node, applied accepts divided by applied-plus-failed accepts over the window.*\n\n###### Reading it:\n*Green (>=90% applied) is a healthy drain; yellow is degraded; red means accepts are mostly failing.*\n\n###### Healthy range:\n*At or near 100% applied when the queue is draining healthily; workload-dependent.*\n\n###### Watch for:\n*A node dropping into yellow or red, which signals queue pressure, under-bidding, or fee escalation on that node.*\n\n###### Keywords:\n- **Transaction queue (TxQ)** *(per node)* \u2014 holds transactions that meet local cost but not the open-ledger cost, to include in a later ledger.\n- **Queue accept (drain)** *(per node)* \u2014 moving queued transactions into a newly closed ledger; the applied share shows drain health.\n- **Fee escalation** *(per node)* \u2014 the exponential rise in the open-ledger cost once the ledger exceeds its soft transaction target.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[TxQ.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/detail/TxQ.cpp)\n\n###### Function:\n`TxQ::accept`\n\n###### References:\n[Transaction queue (TxQ)](https://xrpl.org/docs/concepts/transactions/transaction-queue) \u00b7 [Fee escalation](https://xrpl.org/docs/concepts/transactions/transaction-cost#open-ledger-cost) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#transaction-queue-txq)", "type": "state-timeline", "gridPos": { "h": 8, @@ -431,7 +471,8 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, "expr": "sum by (service_instance_id) (increase(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"txq.accept_tx\", txq_status=\"applied\"}[$__rate_interval]))\n/\nsum by (service_instance_id) (increase(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"txq.accept_tx\", txq_status=~\"applied|failed\"}[$__rate_interval]))", "interval": "15s", @@ -475,7 +516,7 @@ }, { "title": "Tx Apply Pipeline Latency by Stage (p95)", - "description": "###### What this is:\n*The 95th-percentile duration of each apply-pipeline stage.*\n\n###### How it's computed:\n*95th-percentile of per-stage durations over 5 minutes, grouped by stage and node.*\n\n###### Reading it:\n*Lower is better; isolates which stage dominates processing time.*\n\n###### Healthy range:\n*Each stage typically sub-millisecond to a few ms; workload-dependent.*\n\n###### Watch for:\n*One stage's latency rising, pinpointing the bottleneck in transaction handling.*\n\n###### Source:\n[applySteps.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/tx/applySteps.cpp) \u00b7 [Transactor.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/tx/Transactor.cpp)\n\n###### Function:\n`makeStageSpan ; Transactor::operator()`", + "description": "###### What this is:\n*The 95th-percentile duration of each apply-pipeline stage.*\n\n###### How it's computed:\n*95th-percentile of per-stage durations over 5 minutes, grouped by stage and node.*\n\n###### Reading it:\n*Lower is better; isolates which stage dominates processing time.*\n\n###### Healthy range:\n*Each stage typically sub-millisecond to a few ms; workload-dependent.*\n\n###### Watch for:\n*One stage's latency rising, pinpointing the bottleneck in transaction handling.*\n\n###### Keywords:\n- **Apply pipeline stages** *(per node)* \u2014 the ordered checks a transaction passes \u2014 preflight (stateless), preclaim (stateful), then apply.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[applySteps.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/tx/applySteps.cpp) \u00b7 [Transactor.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/tx/Transactor.cpp)\n\n###### Function:\n`makeStageSpan ; Transactor::operator()`\n\n###### References:\n[Apply pipeline stages](https://xrpl.org/docs/concepts/transactions) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#apply-pipeline-stages)", "type": "timeseries", "gridPos": { "h": 8, @@ -498,14 +539,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(label_replace(label_replace(label_replace(histogram_quantile(0.95, sum by (le, stage, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_duration_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=~\"tx.preflight|tx.preclaim|tx.transactor\", stage=~\"$stage\"}[5m]))), \"stage\", \"Preflight\", \"stage\", \"preflight\"), \"stage\", \"Preclaim\", \"stage\", \"preclaim\"), \"stage\", \"Apply\", \"stage\", \"apply\"), \"series\", \"$1\", \"stage\", \"(.*)\")" + "expr": "label_replace(label_join(label_replace(label_replace(label_replace(label_replace(histogram_quantile(0.95, sum by (le, stage, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_duration_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=~\"tx.preflight|tx.preclaim|tx.transactor\", stage=~\"$stage\"}[5m]))), \"stage\", \"Preflight\", \"stage\", \"preflight\"), \"stage\", \"Preclaim\", \"stage\", \"preclaim\"), \"stage\", \"Apply\", \"stage\", \"apply\"), \"series\", \"$1\", \"stage\", \"(.*)\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "ms", "custom": { "axisLabel": "Duration (ms)", @@ -521,7 +563,7 @@ }, { "title": "Tx Apply Pipeline Failure Rate by Stage", - "description": "###### What this is:\n*How many transactions fail at each apply-pipeline stage per second.*\n\n###### How it's computed:\n*Per-second rate of non-success outcomes over 5 minutes, grouped by stage and node.*\n\n###### Reading it:\n*Shows whether rejections concentrate in preflight, preclaim, or apply.*\n\n###### Healthy range:\n*Workload-dependent; a modest background of expected rejections is normal.*\n\n###### Watch for:\n*A failure spike concentrated in one stage, consistent with malformed or spam submissions.*\n\n###### Source:\n[applySteps.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/tx/applySteps.cpp) \u00b7 [Transactor.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/tx/Transactor.cpp)\n\n###### Function:\n`makeStageSpan ; Transactor::operator()`", + "description": "###### What this is:\n*How many transactions fail at each apply-pipeline stage per second.*\n\n###### How it's computed:\n*Per-second rate of non-success outcomes over 5 minutes, grouped by stage and node.*\n\n###### Reading it:\n*Shows whether rejections concentrate in preflight, preclaim, or apply.*\n\n###### Healthy range:\n*Workload-dependent; a modest background of expected rejections is normal.*\n\n###### Watch for:\n*A failure spike concentrated in one stage, consistent with malformed or spam submissions.*\n\n###### Keywords:\n- **Apply pipeline stages** *(per node)* \u2014 the ordered checks a transaction passes \u2014 preflight (stateless), preclaim (stateful), then apply.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[applySteps.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/tx/applySteps.cpp) \u00b7 [Transactor.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/tx/Transactor.cpp)\n\n###### Function:\n`makeStageSpan ; Transactor::operator()`\n\n###### References:\n[Apply pipeline stages](https://xrpl.org/docs/concepts/transactions) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#apply-pipeline-stages)", "type": "timeseries", "gridPos": { "h": 8, @@ -544,14 +586,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(label_replace(label_replace(label_replace(sum by (stage, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=~\"tx.preflight|tx.preclaim|tx.transactor\", stage=~\"$stage\", ter_result!~\"tesSUCCESS|\"}[$__rate_interval])), \"stage\", \"Preflight\", \"stage\", \"preflight\"), \"stage\", \"Preclaim\", \"stage\", \"preclaim\"), \"stage\", \"Apply\", \"stage\", \"apply\"), \"series\", \"$1\", \"stage\", \"(.*)\")" + "expr": "label_replace(label_join(label_replace(label_replace(label_replace(label_replace(sum by (stage, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=~\"tx.preflight|tx.preclaim|tx.transactor\", stage=~\"$stage\", ter_result!~\"tesSUCCESS|\"}[$__rate_interval])), \"stage\", \"Preflight\", \"stage\", \"preflight\"), \"stage\", \"Preclaim\", \"stage\", \"preclaim\"), \"stage\", \"Apply\", \"stage\", \"apply\"), \"series\", \"$1\", \"stage\", \"(.*)\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: failures/s", "custom": { "axisLabel": "Failures / Sec", @@ -567,7 +610,7 @@ }, { "title": "Tx Apply Pipeline Latency by Type and Stage (p95)", - "description": "###### What this is:\n*The 95th-percentile stage duration broken down by both transaction type and pipeline stage.*\n\n###### How it's computed:\n*95th-percentile durations over 5 minutes, grouped by type and stage; higher cardinality than the by-stage view.*\n\n###### Reading it:\n*Lower is better; shows which stage dominates each type's latency.*\n\n###### Healthy range:\n*Workload-dependent; most type/stage pairs sub-millisecond to a few ms.*\n\n###### Watch for:\n*A specific type/stage combination rising sharply, pinpointing a costly path.*\n\n###### Source:\n[applySteps.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/tx/applySteps.cpp) \u00b7 [Transactor.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/tx/Transactor.cpp)\n\n###### Function:\n`makeStageSpan ; Transactor::operator()`", + "description": "###### What this is:\n*The 95th-percentile stage duration broken down by both transaction type and pipeline stage.*\n\n###### How it's computed:\n*95th-percentile durations over 5 minutes, grouped by type and stage; higher cardinality than the by-stage view.*\n\n###### Reading it:\n*Lower is better; shows which stage dominates each type's latency.*\n\n###### Healthy range:\n*Workload-dependent; most type/stage pairs sub-millisecond to a few ms.*\n\n###### Watch for:\n*A specific type/stage combination rising sharply, pinpointing a costly path.*\n\n###### Keywords:\n- **Transaction type** *(network-wide)* \u2014 the kind of transaction (Payment, OfferCreate, TrustSet, AMM*, NFToken*, etc.), used as a breakdown dimension.\n- **Apply pipeline stages** *(per node)* \u2014 the ordered checks a transaction passes \u2014 preflight (stateless), preclaim (stateful), then apply.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[applySteps.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/tx/applySteps.cpp) \u00b7 [Transactor.cpp](https://github.com/XRPLF/rippled/blob/develop/src/libxrpl/tx/Transactor.cpp)\n\n###### Function:\n`makeStageSpan ; Transactor::operator()`\n\n###### References:\n[Transaction type](https://xrpl.org/docs/references/protocol/transactions/types) \u00b7 [Apply pipeline stages](https://xrpl.org/docs/concepts/transactions) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#transaction-type)", "type": "timeseries", "gridPos": { "h": 8, @@ -590,10 +633,10 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(label_replace(label_replace(histogram_quantile(0.95, sum by (le, tx_type, stage, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_duration_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=~\"tx.preflight|tx.preclaim|tx.transactor\", tx_type=~\"$tx_type\", stage=~\"$stage\"}[5m]))), \"stage\", \"Preflight\", \"stage\", \"preflight\"), \"stage\", \"Preclaim\", \"stage\", \"preclaim\"), \"stage\", \"Apply\", \"stage\", \"apply\")", - "legendFormat": "{{tx_type}} [{{stage}}, {{service_instance_id}}] [{{xrpl_branch}}, {{xrpl_node_role}}]" + "expr": "label_replace(label_join(label_replace(label_replace(label_replace(label_replace(histogram_quantile(0.95, sum by (le, tx_type, stage, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_duration_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=~\"tx.preflight|tx.preclaim|tx.transactor\", tx_type=~\"$tx_type\", stage=~\"$stage\"}[5m]))), \"stage\", \"Preflight\", \"stage\", \"preflight\"), \"stage\", \"Preclaim\", \"stage\", \"preclaim\"), \"stage\", \"Apply\", \"stage\", \"apply\"), \"series\", \"$1\", \"tx_type\", \"(.*)\"), \"xrpl_ident\", \", \", \"stage\", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { @@ -605,7 +648,8 @@ "insertNulls": false, "showPoints": "auto", "pointSize": 3 - } + }, + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}" }, "overrides": [] }, @@ -613,7 +657,7 @@ }, { "title": "Transaction Apply Duration per Ledger", - "description": "###### What this is:\n*The 95th-percentile time to apply the agreed transaction set into each new ledger.*\n\n###### How it's computed:\n*95th-percentile of transaction-apply durations over 5 minutes, per node.*\n\n###### Reading it:\n*Lower is better; a major component of ledger build time.*\n\n###### Healthy range:\n*A few to tens of milliseconds; scales with transactions per ledger.*\n\n###### Watch for:\n*Rising durations during heavy or expensive transaction sets.*\n\n###### Source:\n[BuildLedger.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/detail/BuildLedger.cpp)\n\n###### Function:\n`applyTransactions`", + "description": "###### What this is:\n*The 95th-percentile time to apply the agreed transaction set into each new ledger.*\n\n###### How it's computed:\n*95th-percentile of transaction-apply durations over 5 minutes, per node.*\n\n###### Reading it:\n*Lower is better; a major component of ledger build time.*\n\n###### Healthy range:\n*A few to tens of milliseconds; scales with transactions per ledger.*\n\n###### Watch for:\n*Rising durations during heavy or expensive transaction sets.*\n\n###### Keywords:\n- **Transaction apply phase** *(per node)* \u2014 the step that executes the agreed transaction set into the new ledger during a close.\n- **Ledger build** *(per node)* \u2014 constructing the new ledger by applying the agreed transaction set to the prior ledger.\n- **In-ledger vs target count** *(per node)* \u2014 transactions already in the open ledger versus the soft target that triggers fee escalation.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[BuildLedger.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/ledger/detail/BuildLedger.cpp)\n\n###### Function:\n`applyTransactions`\n\n###### References:\n[Transaction apply phase](https://xrpl.org/docs/concepts/ledgers/open-closed-validated-ledgers) \u00b7 [In-ledger vs target count](https://xrpl.org/docs/concepts/transactions/transaction-cost#open-ledger-cost) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#transaction-apply-phase)", "type": "timeseries", "gridPos": { "h": 8, @@ -631,14 +675,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(histogram_quantile(0.95, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_duration_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"tx.apply\"}[5m]))), \"series\", \"tx.apply\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(histogram_quantile(0.95, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_duration_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"tx.apply\"}[5m]))), \"series\", \"tx.apply\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "ms", "custom": { "axisLabel": "Latency (ms)", @@ -654,7 +699,7 @@ }, { "title": "TxQ Enqueue Rate by Transaction Type", - "description": "###### What this is:\n*How many transactions are queued (rather than applied immediately) per second, by type.*\n\n###### How it's computed:\n*Per-second rate of queue-enqueue operations over 5 minutes, grouped by transaction type and node.*\n\n###### Reading it:\n*Shows the demand mix hitting the queue and how it shifts as the queue fills.*\n\n###### Healthy range:\n*Workload-dependent; low or zero when the network is not congested.*\n\n###### Watch for:\n*A spam burst of one type, an early indicator of fee escalation.*\n\n###### Source:\n[TxQ.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/detail/TxQ.cpp)\n\n###### Function:\n`TxQ::apply`", + "description": "###### What this is:\n*How many transactions are queued (rather than applied immediately) per second, by type.*\n\n###### How it's computed:\n*Per-second rate of queue-enqueue operations over 5 minutes, grouped by transaction type and node.*\n\n###### Reading it:\n*Shows the demand mix hitting the queue and how it shifts as the queue fills.*\n\n###### Healthy range:\n*Workload-dependent; low or zero when the network is not congested.*\n\n###### Watch for:\n*A spam burst of one type, an early indicator of fee escalation.*\n\n###### Keywords:\n- **Direct apply vs enqueue** *(per node)* \u2014 whether a transaction applied straight to the open ledger or was placed in the queue for a later ledger.\n- **Transaction type** *(network-wide)* \u2014 the kind of transaction (Payment, OfferCreate, TrustSet, AMM*, NFToken*, etc.), used as a breakdown dimension.\n- **Fee escalation** *(per node)* \u2014 the exponential rise in the open-ledger cost once the ledger exceeds its soft transaction target.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[TxQ.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/detail/TxQ.cpp)\n\n###### Function:\n`TxQ::apply`\n\n###### References:\n[Direct apply vs enqueue](https://xrpl.org/docs/concepts/transactions/transaction-queue) \u00b7 [Transaction type](https://xrpl.org/docs/references/protocol/transactions/types) \u00b7 [Fee escalation](https://xrpl.org/docs/concepts/transactions/transaction-cost#open-ledger-cost) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#direct-apply-vs-enqueue)", "type": "timeseries", "gridPos": { "h": 8, @@ -672,14 +717,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (tx_type, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"txq.enqueue\"}[$__rate_interval])), \"series\", \"$1\", \"tx_type\", \"(.*)\")" + "expr": "label_replace(label_join(label_replace(sum by (tx_type, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"txq.enqueue\"}[$__rate_interval])), \"series\", \"$1\", \"tx_type\", \"(.*)\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: enqueues/s", "custom": { "axisLabel": "Enqueues / Sec", @@ -695,7 +741,7 @@ }, { "title": "Queue Bypass Ratio (Direct Apply vs Enqueue)", - "description": "###### What this is:\n*The fraction of transactions that applied straight to the open ledger instead of being queued.*\n\n###### How it's computed:\n*Direct-apply rate divided by direct-apply plus enqueue rate over 5 minutes, per node.*\n\n###### Reading it:\n*Higher is better; near 1.0 means the network is not congested.*\n\n###### Healthy range:\n*Close to 1.0 when not congested; falls as demand exceeds capacity.*\n\n###### Watch for:\n*A falling ratio, the cleanest single signal the network has entered sustained fee escalation.*\n\n###### Source:\n[TxQ.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/detail/TxQ.cpp)\n\n###### Function:\n`TxQ::tryDirectApply ; TxQ::apply`", + "description": "###### What this is:\n*The fraction of transactions that applied straight to the open ledger instead of being queued.*\n\n###### How it's computed:\n*Direct-apply rate divided by direct-apply plus enqueue rate over 5 minutes, per node.*\n\n###### Reading it:\n*Higher is better; near 1.0 means the network is not congested.*\n\n###### Healthy range:\n*Close to 1.0 when not congested; falls as demand exceeds capacity.*\n\n###### Watch for:\n*A falling ratio, the cleanest single signal the network has entered sustained fee escalation.*\n\n###### Keywords:\n- **Direct apply vs enqueue** *(per node)* \u2014 whether a transaction applied straight to the open ledger or was placed in the queue for a later ledger.\n- **Fee escalation** *(per node)* \u2014 the exponential rise in the open-ledger cost once the ledger exceeds its soft transaction target.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[TxQ.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/detail/TxQ.cpp)\n\n###### Function:\n`TxQ::tryDirectApply ; TxQ::apply`\n\n###### References:\n[Direct apply vs enqueue](https://xrpl.org/docs/concepts/transactions/transaction-queue) \u00b7 [Fee escalation](https://xrpl.org/docs/concepts/transactions/transaction-cost#open-ledger-cost) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#direct-apply-vs-enqueue)", "type": "timeseries", "gridPos": { "h": 8, @@ -713,14 +759,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"txq.apply_direct\"}[$__rate_interval])) / clamp_min(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"txq.apply_direct\"}[$__rate_interval])) + sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"txq.enqueue\"}[$__rate_interval])), 1), \"series\", \"Direct-Apply Fraction\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"txq.apply_direct\"}[$__rate_interval])) / clamp_min(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"txq.apply_direct\"}[$__rate_interval])) + sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"txq.enqueue\"}[$__rate_interval])), 1), \"series\", \"Direct-Apply Fraction\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "percentunit", "custom": { "axisLabel": "Bypass Fraction", @@ -736,7 +783,7 @@ }, { "title": "Queue Accept (Drain) Duration per Ledger", - "description": "###### What this is:\n*The 95th-percentile time to drain queued transactions into a newly closed ledger.*\n\n###### How it's computed:\n*95th-percentile of queue-drain durations over 5 minutes, per node.*\n\n###### Reading it:\n*Lower is better; rising time signals queue pressure at ledger close.*\n\n###### Healthy range:\n*A few milliseconds when the queue is light; workload-dependent.*\n\n###### Watch for:\n*A sustained climb, indicating a large or contended queue slowing ledger close.*\n\n###### Source:\n[TxQ.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/detail/TxQ.cpp)\n\n###### Function:\n`TxQ::accept`", + "description": "###### What this is:\n*The 95th-percentile time to drain queued transactions into a newly closed ledger.*\n\n###### How it's computed:\n*95th-percentile of queue-drain durations over 5 minutes, per node.*\n\n###### Reading it:\n*Lower is better; rising time signals queue pressure at ledger close.*\n\n###### Healthy range:\n*A few milliseconds when the queue is light; workload-dependent.*\n\n###### Watch for:\n*A sustained climb, indicating a large or contended queue slowing ledger close.*\n\n###### Keywords:\n- **Queue accept (drain)** *(per node)* \u2014 moving queued transactions into a newly closed ledger; the applied share shows drain health.\n- **Ledger close** *(network event)* \u2014 the current open ledger is closed and a new closed ledger is built from the agreed transaction set.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[TxQ.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/detail/TxQ.cpp)\n\n###### Function:\n`TxQ::accept`\n\n###### References:\n[Queue accept (drain)](https://xrpl.org/docs/concepts/transactions/transaction-queue) \u00b7 [Ledger close](https://xrpl.org/docs/concepts/ledgers/open-closed-validated-ledgers) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#queue-accept-drain)", "type": "timeseries", "gridPos": { "h": 8, @@ -754,14 +801,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(histogram_quantile(0.95, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_duration_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"txq.accept\"}[5m]))), \"series\", \"Drain\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(histogram_quantile(0.95, sum by (le, service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_duration_milliseconds_bucket{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"txq.accept\"}[5m]))), \"series\", \"Drain\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "ms", "custom": { "axisLabel": "Duration (ms)", @@ -777,7 +825,7 @@ }, { "title": "Queue Cleanup Rate (Expired Entries)", - "description": "###### What this is:\n*How often expired transactions are removed from the queue each ledger, per second.*\n\n###### How it's computed:\n*Per-second rate of queue-cleanup operations over 5 minutes, per node.*\n\n###### Reading it:\n*Low is healthy; a rising rate means submitters are abandoning transactions.*\n\n###### Healthy range:\n*Near zero when not congested; workload-dependent.*\n\n###### Watch for:\n*A rising rate, a demand-frustration signal where submitters under-bid the escalating fee and gave up.*\n\n###### Source:\n[TxQ.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/detail/TxQ.cpp)\n\n###### Function:\n`TxQ::processClosedLedger`", + "description": "###### What this is:\n*How often expired transactions are removed from the queue each ledger, per second.*\n\n###### How it's computed:\n*Per-second rate of queue-cleanup operations over 5 minutes, per node.*\n\n###### Reading it:\n*Low is healthy; a rising rate means submitters are abandoning transactions.*\n\n###### Healthy range:\n*Near zero when not congested; workload-dependent.*\n\n###### Watch for:\n*A rising rate, a demand-frustration signal where submitters under-bid the escalating fee and gave up.*\n\n###### Keywords:\n- **Queue expiry / abandonment** *(per node)* \u2014 removing a queued transaction whose LastLedgerSequence deadline passed before inclusion.\n- **Fee escalation** *(per node)* \u2014 the exponential rise in the open-ledger cost once the ledger exceeds its soft transaction target.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Recorded in code as a trace span, turned into a metric by the collector (SpanMetrics connector), then aggregated by the Grafana query.*\n\n###### Source:\n[TxQ.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/app/misc/detail/TxQ.cpp)\n\n###### Function:\n`TxQ::processClosedLedger`\n\n###### References:\n[Queue expiry / abandonment](https://xrpl.org/docs/concepts/transactions/reliable-transaction-submission) \u00b7 [Fee escalation](https://xrpl.org/docs/concepts/transactions/transaction-cost#open-ledger-cost) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#queue-expiry-abandonment)", "type": "timeseries", "gridPos": { "h": 8, @@ -795,14 +843,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"txq.cleanup\"}[$__rate_interval])), \"series\", \"Cleanups / Sec\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(span_calls_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\", span_name=\"txq.cleanup\"}[$__rate_interval])), \"series\", \"Cleanups / Sec\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: cleanups/s", "custom": { "axisLabel": "Cleanups / Sec", @@ -821,6 +870,19 @@ "tags": ["transactions"], "templating": { "list": [ + { + "name": "DS_PROMETHEUS", + "type": "datasource", + "label": "Prometheus", + "query": "prometheus", + "regex": "", + "current": {}, + "hide": 0, + "refresh": 1, + "includeAll": false, + "multi": false, + "options": [] + }, { "name": "service_name", "label": "Service Name", @@ -829,7 +891,7 @@ "query": "label_values(service_name)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -849,7 +911,7 @@ "query": "label_values(deployment_environment)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -869,7 +931,7 @@ "query": "label_values(xrpl_network_type)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -949,7 +1011,7 @@ "query": "label_values(target_info, service_instance_id)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -969,7 +1031,7 @@ "query": "label_values(span_calls_total{span_name=\"tx.process\"}, local)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -985,7 +1047,8 @@ "name": "tx_type", "type": "query", "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, "query": "label_values(span_calls_total{span_name=\"tx.process\", tx_type!=\"\"}, tx_type)", "refresh": 2, @@ -1003,7 +1066,8 @@ "name": "ter_result", "type": "query", "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, "query": "label_values(span_calls_total{span_name=\"tx.process\", ter_result!=\"\"}, ter_result)", "refresh": 2, @@ -1021,7 +1085,8 @@ "name": "stage", "type": "query", "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, "query": "label_values(span_calls_total{span_name=~\"tx.preflight|tx.preclaim|tx.transactor\", stage!=\"\"}, stage)", "refresh": 2, @@ -1043,6 +1108,6 @@ }, "title": "Transaction Overview", "uid": "transaction-overview", - "refresh": "5s", - "description": "What this shows: Transaction flow through this node: receipt, processing, results, per-stage timing, and queue behavior.\nUse it to: Trace transactions from arrival to ledger, and locate stalls in processing or the queue." + "refresh": "30s", + "description": "What this shows: Transaction flow through this node: receipt, processing, results, per-stage timing, and queue behavior. \u2014 Use it to: Trace transactions from arrival to ledger, and locate stalls in processing or the queue." } diff --git a/docker/telemetry/grafana/dashboards/validator-health.json b/docker/telemetry/grafana/dashboards/validator-health.json index e1f1abf1f4..4e32e7e606 100644 --- a/docker/telemetry/grafana/dashboards/validator-health.json +++ b/docker/telemetry/grafana/dashboards/validator-health.json @@ -1,8 +1,38 @@ { "annotations": { - "list": [] + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + }, + { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": false, + "iconColor": "rgb(70, 70, 70)", + "name": "Annotate perf-iac runs", + "target": { + "limit": 100, + "matchAny": false, + "tags": ["perf-iac"], + "type": "tags" + }, + "type": "tags" + } + ] }, - "description": "What this shows: Validator agreement rates, validation counts, amendment and UNL status, server state tracking, and ledger close rate.\nUse it to: Confirm the validator is participating, agreeing with the network, and closing ledgers at the expected cadence.", + "description": "What this shows: Validator agreement rates, validation counts, amendment and UNL status, server state tracking, and ledger close rate. \u2014 Use it to: Confirm the validator is participating, agreeing with the network, and closing ledgers at the expected cadence.", "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 1, @@ -23,7 +53,7 @@ }, { "title": "Agreement % (1h)", - "description": "###### What this is:\n*Share of ledgers over the last hour where this validator agreed with the network consensus.*\n\n###### How it's computed:\n*Instantaneous gauge reading of the 1-hour agreement percentage.*\n\n###### Reading it:\n*Higher is better; green at 95%+, yellow from 80%, red below.*\n\n###### Healthy range:\n*95-100%.*\n\n###### Watch for:\n*Values below 80%, meaning the validator frequently disagrees with consensus.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerValidationAgreementGauge`", + "description": "###### What this is:\n*Share of ledgers over the last hour where this validator agreed with the network consensus.*\n\n###### How it's computed:\n*Instantaneous gauge reading of the 1-hour agreement percentage.*\n\n###### Reading it:\n*Higher is better; green at 95%+, yellow from 80%, red below.*\n\n###### Healthy range:\n*95-100%.*\n\n###### Watch for:\n*Values below 80%, meaning the validator frequently disagrees with consensus.*\n\n###### Keywords:\n- **Consensus** *(network event)* \u2014 the protocol by which validators agree on the next ledger's transaction set and close time.\n- **Validation agreement** *(per node)* \u2014 the share of ledgers where this validator's validation matched network consensus versus was missed.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerValidationAgreementGauge`\n\n###### References:\n[Consensus](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) \u00b7 [Validation agreement](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure#validation) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#consensus)", "type": "stat", "gridPos": { "h": 8, @@ -41,14 +71,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(validation_agreement{metric=\"agreement_pct_1h\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Agreement 1h\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(validation_agreement{metric=\"agreement_pct_1h\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Agreement 1h\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "percent", "min": 0, "max": 100, @@ -75,7 +106,7 @@ }, { "title": "Agreement % (24h)", - "description": "###### What this is:\n*Share of ledgers over the last 24 hours where this validator agreed with consensus.*\n\n###### How it's computed:\n*Instantaneous gauge reading of the 24-hour agreement percentage.*\n\n###### Reading it:\n*Higher is better; a smoother, longer-term view than the 1h stat.*\n\n###### Healthy range:\n*95-100%.*\n\n###### Watch for:\n*A sustained dip below 90%, which can indicate configuration drift or a network partition.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerValidationAgreementGauge`", + "description": "###### What this is:\n*Share of ledgers over the last 24 hours where this validator agreed with consensus.*\n\n###### How it's computed:\n*Instantaneous gauge reading of the 24-hour agreement percentage.*\n\n###### Reading it:\n*Higher is better; a smoother, longer-term view than the 1h stat.*\n\n###### Healthy range:\n*95-100%.*\n\n###### Watch for:\n*A sustained dip below 90%, which can indicate configuration drift or a network partition.*\n\n###### Keywords:\n- **Validation agreement** *(per node)* \u2014 the share of ledgers where this validator's validation matched network consensus versus was missed.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerValidationAgreementGauge`\n\n###### References:\n[Validation agreement](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure#validation) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#validation-agreement)", "type": "stat", "gridPos": { "h": 8, @@ -93,14 +124,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(validation_agreement{metric=\"agreement_pct_24h\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Agreement 24h\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(validation_agreement{metric=\"agreement_pct_24h\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Agreement 24h\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "percent", "min": 0, "max": 100, @@ -127,7 +159,7 @@ }, { "title": "Agreements vs Missed (1h)", - "description": "###### What this is:\n*Counts of agreed versus missed validations over the last hour.*\n\n###### How it's computed:\n*Instantaneous gauge readings of the 1-hour agreed and missed counts.*\n\n###### Reading it:\n*Agreements should dominate; missed should be small.*\n\n###### Healthy range:\n*Missed near 0.*\n\n###### Watch for:\n*A high missed count, meaning the validator is skipping consensus rounds.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerValidationAgreementGauge`", + "description": "###### What this is:\n*Counts of agreed versus missed validations over the last hour.*\n\n###### How it's computed:\n*Instantaneous gauge readings of the 1-hour agreed and missed counts.*\n\n###### Reading it:\n*Agreements should dominate; missed should be small.*\n\n###### Healthy range:\n*Missed near 0.*\n\n###### Watch for:\n*A high missed count, meaning the validator is skipping consensus rounds.*\n\n###### Keywords:\n- **Validation agreement** *(per node)* \u2014 the share of ledgers where this validator's validation matched network consensus versus was missed.\n- **Consensus round** *(network event)* \u2014 one propose-and-revise iteration of consensus; several may run before validators converge on a ledger.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerValidationAgreementGauge`\n\n###### References:\n[Validation agreement](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure#validation) \u00b7 [Consensus round](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#validation-agreement)", "type": "bargauge", "gridPos": { "h": 8, @@ -147,20 +179,22 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(validation_agreement{metric=\"agreements_1h\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Agreements 1h\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(validation_agreement{metric=\"agreements_1h\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Agreements 1h\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(validation_agreement{metric=\"missed_1h\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Missed 1h\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(validation_agreement{metric=\"missed_1h\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Missed 1h\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "short", "thresholds": { "steps": [ @@ -193,7 +227,7 @@ }, { "title": "Agreements vs Missed (24h)", - "description": "###### What this is:\n*Counts of agreed versus missed validations over the last 24 hours.*\n\n###### How it's computed:\n*Instantaneous gauge readings of the 24-hour agreed and missed counts.*\n\n###### Reading it:\n*Agreements should dominate over the full day.*\n\n###### Healthy range:\n*Missed a small fraction of agreements.*\n\n###### Watch for:\n*A growing missed share, signalling longer-term reliability problems.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerValidationAgreementGauge`", + "description": "###### What this is:\n*Counts of agreed versus missed validations over the last 24 hours.*\n\n###### How it's computed:\n*Instantaneous gauge readings of the 24-hour agreed and missed counts.*\n\n###### Reading it:\n*Agreements should dominate over the full day.*\n\n###### Healthy range:\n*Missed a small fraction of agreements.*\n\n###### Watch for:\n*A growing missed share, signalling longer-term reliability problems.*\n\n###### Keywords:\n- **Validation agreement** *(per node)* \u2014 the share of ledgers where this validator's validation matched network consensus versus was missed.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerValidationAgreementGauge`\n\n###### References:\n[Validation agreement](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure#validation) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#validation-agreement)", "type": "bargauge", "gridPos": { "h": 8, @@ -213,20 +247,22 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(validation_agreement{metric=\"agreements_24h\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Agreements 24h\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(validation_agreement{metric=\"agreements_24h\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Agreements 24h\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(validation_agreement{metric=\"missed_24h\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Missed 24h\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(validation_agreement{metric=\"missed_24h\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Missed 24h\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "short", "thresholds": { "steps": [ @@ -271,7 +307,7 @@ }, { "title": "Validation Rate", - "description": "###### What this is:\n*Validations this node sends per minute.*\n\n###### How it's computed:\n*Per-second rate of the sent-validations counter over 5 minutes, scaled to per minute.*\n\n###### Reading it:\n*Should track the ledger close cadence; roughly one validation per closed ledger.*\n\n###### Healthy range:\n*About 12-20 per minute (one per closed ledger, ~3-5s close).*\n\n###### Watch for:\n*A drop toward zero, meaning the validator has stopped participating.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`incrementValidationsSent (caller RCLConsensus.cpp)`", + "description": "###### What this is:\n*Validations this node sends per minute.*\n\n###### How it's computed:\n*Per-second rate of the sent-validations counter over 5 minutes, scaled to per minute.*\n\n###### Reading it:\n*Should track the ledger close cadence; roughly one validation per closed ledger.*\n\n###### Healthy range:\n*About 12-20 per minute (one per closed ledger, ~3-5s close).*\n\n###### Watch for:\n*A drop toward zero, meaning the validator has stopped participating.*\n\n###### Keywords:\n- **Validations checked vs sent** *(per node)* \u2014 validations this node received from peers and checked, versus validations it issued itself.\n- **Ledger close interval** *(network-wide)* \u2014 the network's steady ledger rhythm \u2014 roughly one closed ledger every 3-5 seconds on Mainnet.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`incrementValidationsSent (caller RCLConsensus.cpp)`\n\n###### References:\n[Validations checked vs sent](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure#validation) \u00b7 [Ledger close interval](https://xrpl.org/docs/concepts/ledgers/ledger-close-times) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#validations-checked-vs-sent)", "type": "stat", "gridPos": { "h": 8, @@ -289,14 +325,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(validations_sent_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) * 60, \"series\", \"Sent/min\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(validations_sent_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) * 60, \"series\", \"Sent/min\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: validations/min", "thresholds": { "steps": [ @@ -321,7 +358,7 @@ }, { "title": "Validations Checked Rate", - "description": "###### What this is:\n*Validations received from peers and checked per minute.*\n\n###### How it's computed:\n*Per-second rate of the checked-validations counter over 5 minutes, scaled to per minute.*\n\n###### Reading it:\n*Reflects how much validation traffic the network is delivering to this node.*\n\n###### Healthy range:\n*Workload-dependent; scales with trusted validator count.*\n\n###### Watch for:\n*A sudden collapse, which suggests peer connectivity loss or network isolation.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`incrementValidationsChecked (caller NetworkOPs.cpp)`", + "description": "###### What this is:\n*Validations received from peers and checked per minute.*\n\n###### How it's computed:\n*Per-second rate of the checked-validations counter over 5 minutes, scaled to per minute.*\n\n###### Reading it:\n*Reflects how much validation traffic the network is delivering to this node.*\n\n###### Healthy range:\n*Workload-dependent; scales with trusted validator count.*\n\n###### Watch for:\n*A sudden collapse, which suggests peer connectivity loss or network isolation.*\n\n###### Keywords:\n- **Validations checked vs sent** *(per node)* \u2014 validations this node received from peers and checked, versus validations it issued itself.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`incrementValidationsChecked (caller NetworkOPs.cpp)`\n\n###### References:\n[Validations checked vs sent](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure#validation) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#validations-checked-vs-sent)", "type": "stat", "gridPos": { "h": 8, @@ -339,14 +376,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(validations_checked_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) * 60, \"series\", \"Checked/min\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(validations_checked_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) * 60, \"series\", \"Checked/min\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: validations/min", "custom": {} }, @@ -355,7 +393,7 @@ }, { "title": "Amendment Blocked", - "description": "###### What this is:\n*Whether the node is amendment-blocked (OK/BLOCKED).*\n\n###### How it's computed:\n*Instantaneous gauge reading of the amendment-blocked flag.*\n\n###### Reading it:\n*OK is healthy; BLOCKED means an enabled amendment is unsupported by this build.*\n\n###### Healthy range:\n*OK.*\n\n###### Watch for:\n*A BLOCKED state, which halts validation and requires a software upgrade.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerValidatorHealthGauge`", + "description": "###### What this is:\n*Whether the node is amendment-blocked (OK/BLOCKED).*\n\n###### How it's computed:\n*Instantaneous gauge reading of the amendment-blocked flag.*\n\n###### Reading it:\n*OK is healthy; BLOCKED means an enabled amendment is unsupported by this build.*\n\n###### Healthy range:\n*OK.*\n\n###### Watch for:\n*A BLOCKED state, which halts validation and requires a software upgrade.*\n\n###### Keywords:\n- **Amendment blocked** *(per node)* \u2014 the node has halted because the network enabled an amendment its software version does not support.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerValidatorHealthGauge`\n\n###### References:\n[Amendment blocked](https://xrpl.org/docs/concepts/networks-and-servers/amendments) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#amendment-blocked)", "type": "stat", "gridPos": { "h": 8, @@ -373,14 +411,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(validator_health{metric=\"amendment_blocked\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Blocked\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(validator_health{metric=\"amendment_blocked\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Blocked\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "none", "mappings": [ { @@ -421,7 +460,7 @@ }, { "title": "UNL Expiry (days)", - "description": "###### What this is:\n*Days remaining until the current UNL (trusted validator list) expires.*\n\n###### How it's computed:\n*Instantaneous gauge reading of days-to-expiry.*\n\n###### Reading it:\n*Higher is safer; green at 30+, yellow under 7, red at expiry.*\n\n###### Healthy range:\n*30+ days.*\n\n###### Watch for:\n*Fewer than 7 days, after which the node loses its trusted validator set if not renewed.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerValidatorHealthGauge`", + "description": "###### What this is:\n*Days remaining until the current UNL (trusted validator list) expires.*\n\n###### How it's computed:\n*Instantaneous gauge reading of days-to-expiry.*\n\n###### Reading it:\n*Higher is safer; green at 30+, yellow under 7, red at expiry.*\n\n###### Healthy range:\n*30+ days.*\n\n###### Watch for:\n*Fewer than 7 days, after which the node loses its trusted validator set if not renewed.*\n\n###### Keywords:\n- **UNL (Unique Node List)** *(per node)* \u2014 the list of validators a node trusts not to collude; the basis for its consensus and quorum.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerValidatorHealthGauge`\n\n###### References:\n[UNL (Unique Node List)](https://xrpl.org/docs/concepts/consensus-protocol/unl) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#unl-unique-node-list)", "type": "stat", "gridPos": { "h": 8, @@ -439,14 +478,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(validator_health{metric=\"unl_expiry_days\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"UNL Expiry\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(validator_health{metric=\"unl_expiry_days\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"UNL Expiry\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "d", "thresholds": { "steps": [ @@ -471,7 +511,7 @@ }, { "title": "UNL Blocked", - "description": "###### What this is:\n*Whether the node's UNL is blocked (OK/BLOCKED).*\n\n###### How it's computed:\n*Instantaneous gauge reading of the UNL-blocked flag.*\n\n###### Reading it:\n*OK is healthy; BLOCKED means validator trust cannot be established.*\n\n###### Healthy range:\n*OK.*\n\n###### Watch for:\n*A BLOCKED state, which can stop the node participating in consensus.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerValidatorHealthGauge`", + "description": "###### What this is:\n*Whether the node's UNL is blocked (OK/BLOCKED).*\n\n###### How it's computed:\n*Instantaneous gauge reading of the UNL-blocked flag.*\n\n###### Reading it:\n*OK is healthy; BLOCKED means validator trust cannot be established.*\n\n###### Healthy range:\n*OK.*\n\n###### Watch for:\n*A BLOCKED state, which can stop the node participating in consensus.*\n\n###### Keywords:\n- **UNL (Unique Node List)** *(per node)* \u2014 the list of validators a node trusts not to collude; the basis for its consensus and quorum.\n- **UNL blocked** *(per node)* \u2014 the node cannot establish a usable trusted validator list, so it cannot safely validate.\n- **Consensus** *(network event)* \u2014 the protocol by which validators agree on the next ledger's transaction set and close time.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerValidatorHealthGauge`\n\n###### References:\n[UNL (Unique Node List)](https://xrpl.org/docs/concepts/consensus-protocol/unl) \u00b7 [UNL blocked](https://xrpl.org/docs/concepts/consensus-protocol) \u00b7 [Consensus](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#unl-unique-node-list)", "type": "stat", "gridPos": { "h": 8, @@ -489,14 +529,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(validator_health{metric=\"unl_blocked\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"UNL Blocked\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(validator_health{metric=\"unl_blocked\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"UNL Blocked\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "none", "mappings": [ { @@ -537,7 +578,7 @@ }, { "title": "Agreement/Missed Counters (Rate)", - "description": "###### What this is:\n*Rate of cumulative agreement and missed-validation counters per minute.*\n\n###### How it's computed:\n*Per-second rate of each monotonic counter over 5 minutes, scaled to per minute.*\n\n###### Reading it:\n*Agreements should dominate; the missed line should stay low.*\n\n###### Healthy range:\n*Missed rate near 0.*\n\n###### Watch for:\n*A rising missed rate, complementing the windowed agreement percentages above.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerValidationTotalsCounters`", + "description": "###### What this is:\n*Rate of cumulative agreement and missed-validation counters per minute.*\n\n###### How it's computed:\n*Per-second rate of each monotonic counter over 5 minutes, scaled to per minute.*\n\n###### Reading it:\n*Agreements should dominate; the missed line should stay low.*\n\n###### Healthy range:\n*Missed rate near 0.*\n\n###### Watch for:\n*A rising missed rate, complementing the windowed agreement percentages above.*\n\n###### Keywords:\n- **Validation agreement** *(per node)* \u2014 the share of ledgers where this validator's validation matched network consensus versus was missed.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerValidationTotalsCounters`\n\n###### References:\n[Validation agreement](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure#validation) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#validation-agreement)", "type": "timeseries", "gridPos": { "h": 8, @@ -555,20 +596,22 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(validation_agreements_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) * 60, \"series\", \"Agreements/min\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(validation_agreements_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) * 60, \"series\", \"Agreements/min\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(validation_missed_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) * 60, \"series\", \"Missed/min\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(validation_missed_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) * 60, \"series\", \"Missed/min\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: validations/min", "custom": { "axisLabel": "Validations / Min", @@ -617,7 +660,7 @@ }, { "title": "Validation Quorum", - "description": "###### What this is:\n*Minimum number of trusted validations required to declare a ledger fully validated.*\n\n###### How it's computed:\n*Instantaneous gauge reading of the current quorum requirement.*\n\n###### Reading it:\n*Tracks the quorum derived from the active validator list; changes when the list changes.*\n\n###### Healthy range:\n*Stable at the network-appropriate value.*\n\n###### Watch for:\n*An unexpected drop, which can weaken consensus safety guarantees.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerValidatorHealthGauge`", + "description": "###### What this is:\n*Minimum number of trusted validations required to declare a ledger fully validated.*\n\n###### How it's computed:\n*Instantaneous gauge reading of the current quorum requirement.*\n\n###### Reading it:\n*Tracks the quorum derived from the active validator list; changes when the list changes.*\n\n###### Healthy range:\n*Stable at the network-appropriate value.*\n\n###### Watch for:\n*An unexpected drop, which can weaken consensus safety guarantees.*\n\n###### Keywords:\n- **Validation quorum** *(network-wide)* \u2014 the minimum number of agreeing trusted validations needed to declare a ledger fully validated.\n- **Validator list** *(network-wide)* \u2014 signed lists of recommended validators (UNLs) that peers distribute to each other.\n- **Consensus** *(network event)* \u2014 the protocol by which validators agree on the next ledger's transaction set and close time.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerValidatorHealthGauge`\n\n###### References:\n[Validation quorum](https://xrpl.org/docs/concepts/consensus-protocol/negative-unl) \u00b7 [Validator list](https://xrpl.org/docs/concepts/consensus-protocol/unl) \u00b7 [Consensus](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#validation-quorum)", "type": "stat", "gridPos": { "h": 8, @@ -635,14 +678,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(validator_health{metric=\"validation_quorum\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Quorum\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(validator_health{metric=\"validation_quorum\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Quorum\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "none", "custom": {} }, @@ -651,13 +695,13 @@ }, { "title": "State Value Timeline", - "description": "###### What this is:\n*Numeric encoding of the server operating state (disconnected, connected, syncing, tracking, full, validating, proposing) over time.*\n\n###### How it's computed:\n*Instantaneous gauge reading of the encoded state value.*\n\n###### Reading it:\n*A flat line at the full-operation state is healthy; steps show transitions.*\n\n###### Healthy range:\n*Steady at the highest (full) state.*\n\n###### Watch for:\n*Frequent transitions, useful for correlating state flapping with other metrics.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerStateTrackingGauge`", + "description": "###### What this is:\n*Numeric encoding of the server operating state (disconnected, connected, syncing, tracking, full, validating, proposing) over time.*\n\n###### How it's computed:\n*Instantaneous gauge reading of the encoded state value.*\n\n###### Reading it:\n*A flat line at the full-operation state is healthy; steps show transitions.*\n\n###### Healthy range:\n*Steady at the highest (full) state.*\n\n###### Watch for:\n*Frequent transitions, useful for correlating state flapping with other metrics.*\n\n###### Keywords:\n- **Operating mode / server state** *(per node)* \u2014 the node's sync level: Disconnected, Connected, Syncing, Tracking, Full (and Validating/Proposing).\n- **Consensus mode** *(per node)* \u2014 the node's role/health in the current round: Proposing, Observing, Wrong Ledger, or Switched Ledger.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerStateTrackingGauge`\n\n###### References:\n[Operating mode / server state](https://xrpl.org/docs/references/http-websocket-apis/api-conventions/xrpld-server-states) \u00b7 [Consensus mode](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#operating-mode-server-state)", "type": "timeseries", "gridPos": { "h": 8, "w": 24, "x": 0, - "y": 123 + "y": 91 }, "options": { "tooltip": { @@ -669,14 +713,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(state_tracking{metric=\"state_value\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"State\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(state_tracking{metric=\"state_value\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"State\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "short", "custom": { "axisLabel": "State", @@ -697,41 +742,7 @@ }, { "title": "Time in Current State", - "description": "###### What this is:\n*How long the server has held its current operating state, in seconds.*\n\n###### How it's computed:\n*Current value of the time-in-state gauge.*\n\n###### Reading it:\n*Not yet wired in the code; the value currently always reads 0.*\n\n###### Healthy range:\n*Not applicable; the value is always 0 today.*\n\n###### Watch for:\n*n/a until implemented.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerStateTrackingGauge`", - "type": "stat", - "gridPos": { - "h": 8, - "w": 24, - "x": 0, - "y": 91 - }, - "options": { - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus" - }, - "expr": "label_replace(state_tracking{metric=\"time_in_current_state_seconds\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Time in State\", \"\", \"\")" - } - ], - "fieldConfig": { - "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", - "unit": "s", - "custom": {} - }, - "overrides": [] - } - }, - { - "title": "State Changes Rate", - "description": "###### What this is:\n*Rate of server operating-state changes per hour.*\n\n###### How it's computed:\n*Per-hour rate of the state-change counter, averaged over a 1-hour window.*\n\n###### Reading it:\n*Near zero is healthy; each increment is one state transition.*\n\n###### Healthy range:\n*Near 0 changes per hour.*\n\n###### Watch for:\n*Frequent transitions, which point to network instability or configuration problems.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`incrementStateChanges (caller NetworkOPs.cpp)`", + "description": "###### What this is:\n*How long the server has held its current operating state, in seconds.*\n\n###### How it's computed:\n*Current value of the time-in-state gauge.*\n\n###### Reading it:\n*Not yet wired in the code; the value currently always reads 0.*\n\n###### Healthy range:\n*Not applicable; the value is always 0 today.*\n\n###### Watch for:\n*n/a until implemented.*\n\n###### Keywords:\n- **Operating mode / server state** *(per node)* \u2014 the node's sync level: Disconnected, Connected, Syncing, Tracking, Full (and Validating/Proposing).\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerStateTrackingGauge`\n\n###### References:\n[Operating mode / server state](https://xrpl.org/docs/references/http-websocket-apis/api-conventions/xrpld-server-states) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#operating-mode-server-state)", "type": "stat", "gridPos": { "h": 8, @@ -749,14 +760,50 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(3600 * sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(state_changes_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])), \"series\", \"Changes/hr\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(state_tracking{metric=\"time_in_current_state_seconds\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Time in State\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", + "unit": "s", + "custom": {} + }, + "overrides": [] + } + }, + { + "title": "State Changes Rate", + "description": "###### What this is:\n*Rate of server operating-state changes per hour.*\n\n###### How it's computed:\n*Per-hour rate of the state-change counter, averaged over a 1-hour window.*\n\n###### Reading it:\n*Near zero is healthy; each increment is one state transition.*\n\n###### Healthy range:\n*Near 0 changes per hour.*\n\n###### Watch for:\n*Frequent transitions, which point to network instability or configuration problems.*\n\n###### Keywords:\n- **Operating mode / server state** *(per node)* \u2014 the node's sync level: Disconnected, Connected, Syncing, Tracking, Full (and Validating/Proposing).\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`incrementStateChanges (caller NetworkOPs.cpp)`\n\n###### References:\n[Operating mode / server state](https://xrpl.org/docs/references/http-websocket-apis/api-conventions/xrpld-server-states) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#operating-mode-server-state)", + "type": "stat", + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 107 + }, + "options": { + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "label_replace(label_join(label_replace(3600 * sum by (service_instance_id, xrpl_branch, xrpl_node_role) (rate(state_changes_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval])), \"series\", \"Changes/hr\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" + } + ], + "fieldConfig": { + "defaults": { + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: changes/hr", "thresholds": { "steps": [ @@ -781,13 +828,13 @@ }, { "title": "Ledgers Closed Rate", - "description": "###### What this is:\n*Ledgers closed per minute by this node.*\n\n###### How it's computed:\n*Per-second rate of the ledgers-closed counter over 5 minutes, scaled to per minute.*\n\n###### Reading it:\n*Should match the network's steady close cadence.*\n\n###### Healthy range:\n*About 12-20 per minute (one per closed ledger, ~3-5s close).*\n\n###### Watch for:\n*Deviation from the expected cadence, which indicates consensus timing trouble or the node falling behind.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`incrementLedgersClosed (caller RCLConsensus.cpp)`", + "description": "###### What this is:\n*Ledgers closed per minute by this node.*\n\n###### How it's computed:\n*Per-second rate of the ledgers-closed counter over 5 minutes, scaled to per minute.*\n\n###### Reading it:\n*Should match the network's steady close cadence.*\n\n###### Healthy range:\n*About 12-20 per minute (one per closed ledger, ~3-5s close).*\n\n###### Watch for:\n*Deviation from the expected cadence, which indicates consensus timing trouble or the node falling behind.*\n\n###### Keywords:\n- **Ledgers closed rate** *(per node)* \u2014 how many ledgers this node closed per minute; should match the network close cadence.\n- **Ledger close interval** *(network-wide)* \u2014 the network's steady ledger rhythm \u2014 roughly one closed ledger every 3-5 seconds on Mainnet.\n- **Consensus** *(network event)* \u2014 the protocol by which validators agree on the next ledger's transaction set and close time.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`incrementLedgersClosed (caller RCLConsensus.cpp)`\n\n###### References:\n[Ledgers closed rate](https://xrpl.org/docs/concepts/ledgers/ledger-close-times) \u00b7 [Consensus](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#ledgers-closed-rate)", "type": "stat", "gridPos": { "h": 8, "w": 24, "x": 0, - "y": 107 + "y": 115 }, "options": { "tooltip": { @@ -799,14 +846,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(rate(ledgers_closed_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) * 60, \"series\", \"Closed/min\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(rate(ledgers_closed_total{service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}[$__rate_interval]) * 60, \"series\", \"Closed/min\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "suffix: ledgers/min", "thresholds": { "steps": [ @@ -831,13 +879,13 @@ }, { "title": "Agreement % (7d)", - "description": "###### What this is:\n*Share of ledgers over the trailing 7 days where this validator agreed with consensus.*\n\n###### How it's computed:\n*Instantaneous gauge reading of the 7-day agreement percentage.*\n\n###### Reading it:\n*The long-term reliability window; higher is better.*\n\n###### Healthy range:\n*95-100%.*\n\n###### Watch for:\n*A gradual decline, which reflects chronic rather than transient disagreement.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerValidationAgreementGauge`", + "description": "###### What this is:\n*Share of ledgers over the trailing 7 days where this validator agreed with consensus.*\n\n###### How it's computed:\n*Instantaneous gauge reading of the 7-day agreement percentage.*\n\n###### Reading it:\n*The long-term reliability window; higher is better.*\n\n###### Healthy range:\n*95-100%.*\n\n###### Watch for:\n*A gradual decline, which reflects chronic rather than transient disagreement.*\n\n###### Keywords:\n- **Validation agreement** *(per node)* \u2014 the share of ledgers where this validator's validation matched network consensus versus was missed.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerValidationAgreementGauge`\n\n###### References:\n[Validation agreement](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure#validation) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#validation-agreement)", "type": "stat", "gridPos": { "h": 8, "w": 24, "x": 0, - "y": 115 + "y": 123 }, "options": { "tooltip": { @@ -849,14 +897,15 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(validation_agreement{metric=\"agreement_pct_7d\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Agreement 7d\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(validation_agreement{metric=\"agreement_pct_7d\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Agreement 7d\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "percent", "min": 0, "max": 100, @@ -883,7 +932,7 @@ }, { "title": "Agreements vs Missed (7d)", - "description": "###### What this is:\n*Agreed versus missed validation counts over the trailing 7 days.*\n\n###### How it's computed:\n*Instantaneous gauge readings of the 7-day agreed and missed counts.*\n\n###### Reading it:\n*Agreements should dominate across the week.*\n\n###### Healthy range:\n*Missed a small fraction of agreements.*\n\n###### Watch for:\n*A rising missed trend, signalling sustained validator unreliability.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerValidationAgreementGauge`", + "description": "###### What this is:\n*Agreed versus missed validation counts over the trailing 7 days.*\n\n###### How it's computed:\n*Instantaneous gauge readings of the 7-day agreed and missed counts.*\n\n###### Reading it:\n*Agreements should dominate across the week.*\n\n###### Healthy range:\n*Missed a small fraction of agreements.*\n\n###### Watch for:\n*A rising missed trend, signalling sustained validator unreliability.*\n\n###### Keywords:\n- **Validation agreement** *(per node)* \u2014 the share of ledgers where this validator's validation matched network consensus versus was missed.\n\n###### Computation boundary:\n*Result: Per node \u2014 each series is one server's own value.*\n*Computed in xrpld code (MetricsRegistry, OpenTelemetry SDK) and exported as a metric; the collector only forwards it; the Grafana query selects and aggregates it.*\n\n###### Source:\n[MetricsRegistry.cpp](https://github.com/XRPLF/rippled/blob/develop/src/xrpld/telemetry/MetricsRegistry.cpp)\n\n###### Function:\n`registerValidationAgreementGauge`\n\n###### References:\n[Validation agreement](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure#validation) \u00b7 [Telemetry glossary](https://github.com/XRPLF/rippled/blob/develop/docs/telemetry-glossary.md#validation-agreement)", "type": "timeseries", "gridPos": { "h": 8, @@ -901,20 +950,22 @@ "targets": [ { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(validation_agreement{metric=\"agreements_7d\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Agreements 7d\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(validation_agreement{metric=\"agreements_7d\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Agreements 7d\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" }, { "datasource": { - "type": "prometheus" + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" }, - "expr": "label_replace(validation_agreement{metric=\"missed_7d\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Missed 7d\", \"\", \"\")" + "expr": "label_replace(label_join(label_replace(validation_agreement{metric=\"missed_7d\",service_instance_id=~\"$node\", deployment_environment=~\"$deployment_environment\", xrpl_network_type=~\"$xrpl_network_type\", service_name=~\"$service_name\", xrpl_work_item=~\"$xrpl_work_item\", xrpl_branch=~\"$xrpl_branch\", xrpl_node_role=~\"$xrpl_node_role\"}, \"series\", \"Missed 7d\", \"\", \"\"), \"xrpl_ident\", \", \", \"service_instance_id\", \"xrpl_branch\", \"xrpl_work_item\"), \"xrpl_ident\", \"[$1]\", \"xrpl_ident\", \"(?:, )*(.*[^, ])(?:, )*\")" } ], "fieldConfig": { "defaults": { - "displayName": "${__field.labels.series} [${__field.labels.service_instance_id} ${__field.labels.xrpl_branch} ${__field.labels.xrpl_node_role} ${__field.labels.xrpl_work_item}]", + "displayName": "${__field.labels.series} ${__field.labels.xrpl_ident}", "unit": "short", "custom": { "axisLabel": "Count", @@ -932,6 +983,19 @@ "tags": ["validator", "health"], "templating": { "list": [ + { + "name": "DS_PROMETHEUS", + "type": "datasource", + "label": "Prometheus", + "query": "prometheus", + "regex": "", + "current": {}, + "hide": 0, + "refresh": 1, + "includeAll": false, + "multi": false, + "options": [] + }, { "name": "service_name", "label": "Service Name", @@ -940,7 +1004,7 @@ "query": "label_values(service_name)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -960,7 +1024,7 @@ "query": "label_values(deployment_environment)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -980,7 +1044,7 @@ "query": "label_values(xrpl_network_type)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -1060,7 +1124,7 @@ "query": "label_values(target_info, service_instance_id)", "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${DS_PROMETHEUS}" }, "includeAll": true, "allValue": ".*", @@ -1080,5 +1144,5 @@ }, "title": "Validator Health", "uid": "validator-health", - "refresh": "5s" + "refresh": "30s" } diff --git a/docker/telemetry/integration-test.sh b/docker/telemetry/integration-test.sh index c179fbde3a..b4401d5906 100755 --- a/docker/telemetry/integration-test.sh +++ b/docker/telemetry/integration-test.sh @@ -27,7 +27,7 @@ REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" XRPLD="$REPO_ROOT/.build/xrpld" COMPOSE_FILE="$SCRIPT_DIR/docker-compose.yml" STANDALONE_CFG="$SCRIPT_DIR/xrpld-telemetry.cfg" -WORKDIR="/tmp/xrpld-integration" +WORKDIR="${WORKDIR:-/tmp/xrpld-integration}" NUM_NODES=6 PEER_PORT_BASE=51235 RPC_PORT_BASE=5005 @@ -93,6 +93,7 @@ check_log_correlation() { local matches matches=$(grep -c 'trace_id=[a-f0-9]\{32\} span_id=[a-f0-9]\{16\}' "$logfile") || matches=0 total_matches=$((total_matches + matches)) + # Capture the first trace_id we find for cross-referencing with Tempo if [ -z "$sample_trace_id" ] && [ "$matches" -gt 0 ]; then sample_trace_id=$(grep -o 'trace_id=[a-f0-9]\{32\}' "$logfile" | head -1 | cut -d= -f2) fi @@ -379,6 +380,12 @@ metrics_endpoint=http://localhost:4318/v1/metrics server=otel endpoint=http://localhost:4318/v1/metrics prefix=rippled +service_instance_id=Node-${i} + +[insight] +server=statsd +address=127.0.0.1:8125 +prefix=rippled [rpc_startup] { "command": "log_level", "severity": "warning" } @@ -577,15 +584,15 @@ log "--- Phase 5: Spanmetrics ---" log "Waiting 20s for Prometheus scrape cycle..." sleep 20 -calls_count=$(curl -sf "$PROM/api/v1/query?query=traces_span_metrics_calls_total" | +calls_count=$(curl -sf "$PROM/api/v1/query?query=span_calls_total" | jq '.data.result | length' 2>/dev/null || echo 0) if [ "$calls_count" -gt 0 ]; then - ok "Prometheus: traces_span_metrics_calls_total ($calls_count series)" + ok "Prometheus: span_calls_total ($calls_count series)" else - fail "Prometheus: traces_span_metrics_calls_total (0 series)" + fail "Prometheus: span_calls_total (0 series)" fi -duration_count=$(curl -sf "$PROM/api/v1/query?query=traces_span_metrics_duration_milliseconds_count" | +duration_count=$(curl -sf "$PROM/api/v1/query?query=span_duration_milliseconds_count" | jq '.data.result | length' 2>/dev/null || echo 0) if [ "$duration_count" -gt 0 ]; then ok "Prometheus: duration histogram ($duration_count series)" @@ -621,22 +628,22 @@ check_otel_metric() { } # Node health gauges (ObservableGauge — no _total suffix) -check_otel_metric "rippled_LedgerMaster_Validated_Ledger_Age" -check_otel_metric "rippled_LedgerMaster_Published_Ledger_Age" -check_otel_metric "rippled_job_count" +check_otel_metric "ledgermaster_validated_ledger_age" +check_otel_metric "ledgermaster_published_ledger_age" +check_otel_metric "job_count" # State accounting -check_otel_metric "rippled_State_Accounting_Full_duration" +check_otel_metric "state_accounting_full_duration" # Peer finder -check_otel_metric "rippled_Peer_Finder_Active_Inbound_Peers" -check_otel_metric "rippled_Peer_Finder_Active_Outbound_Peers" +check_otel_metric "peer_finder_active_inbound_peers" +check_otel_metric "peer_finder_active_outbound_peers" # RPC counters (Counter — Prometheus adds _total suffix automatically) -check_otel_metric "rippled_rpc_requests_total" +check_otel_metric "rpc_requests_total" # Overlay traffic -check_otel_metric "rippled_total_Bytes_In" +check_otel_metric "total_bytes_in" # Verify StatsD receiver is NOT required (no statsd receiver in pipeline) log "" @@ -673,46 +680,46 @@ check_otel_metric() { } # Task 9.1: NodeStore I/O -check_otel_metric 'xrpld_nodestore_state{metric="node_reads_total"}' -check_otel_metric 'xrpld_nodestore_state{metric="write_load"}' +check_otel_metric 'nodestore_state{metric="node_reads_total"}' +check_otel_metric 'nodestore_state{metric="write_load"}' # Task 9.2: Cache hit rates -check_otel_metric 'xrpld_cache_metrics{metric="SLE_hit_rate"}' -check_otel_metric 'xrpld_cache_metrics{metric="treenode_cache_size"}' +check_otel_metric 'cache_metrics{metric="SLE_hit_rate"}' +check_otel_metric 'cache_metrics{metric="treenode_cache_size"}' # Task 9.3: TxQ metrics -check_otel_metric 'xrpld_txq_metrics{metric="txq_count"}' -check_otel_metric 'xrpld_txq_metrics{metric="txq_reference_fee_level"}' +check_otel_metric 'txq_metrics{metric="txq_count"}' +check_otel_metric 'txq_metrics{metric="txq_reference_fee_level"}' # Task 9.4: Per-RPC metrics -check_otel_metric "xrpld_rpc_method_started_total" -check_otel_metric "xrpld_rpc_method_finished_total" +check_otel_metric "rpc_method_started_total" +check_otel_metric "rpc_method_finished_total" # Task 9.5: Per-job metrics -check_otel_metric "xrpld_job_queued_total" -check_otel_metric "xrpld_job_finished_total" +check_otel_metric "job_queued_total" +check_otel_metric "job_finished_total" # Task 9.6: Counted object instances -check_otel_metric "xrpld_object_count" +check_otel_metric "object_count" # Task 9.7: Load factor breakdown -check_otel_metric 'xrpld_load_factor_metrics{metric="load_factor"}' -check_otel_metric 'xrpld_load_factor_metrics{metric="load_factor_server"}' +check_otel_metric 'load_factor_metrics{metric="load_factor"}' +check_otel_metric 'load_factor_metrics{metric="load_factor_server"}' # Task 7.15 / Phase 9: ValidationTracker rolling-window agreement gauge. # MetricsRegistry::registerValidationAgreementGauge() publishes -# xrpld_validation_agreement with a `metric` label for each window +# validation_agreement with a `metric` label for each window # (1h / 24h / 7d) plus the matching agreement/miss counts. The 7-day # window matches the external xrpl-validator-dashboard parity target. -check_otel_metric 'xrpld_validation_agreement{metric="agreement_pct_1h"}' -check_otel_metric 'xrpld_validation_agreement{metric="agreement_pct_24h"}' -check_otel_metric 'xrpld_validation_agreement{metric="agreement_pct_7d"}' -check_otel_metric 'xrpld_validation_agreement{metric="agreements_1h"}' -check_otel_metric 'xrpld_validation_agreement{metric="missed_1h"}' -check_otel_metric 'xrpld_validation_agreement{metric="agreements_24h"}' -check_otel_metric 'xrpld_validation_agreement{metric="missed_24h"}' -check_otel_metric 'xrpld_validation_agreement{metric="agreements_7d"}' -check_otel_metric 'xrpld_validation_agreement{metric="missed_7d"}' +check_otel_metric 'validation_agreement{metric="agreement_pct_1h"}' +check_otel_metric 'validation_agreement{metric="agreement_pct_24h"}' +check_otel_metric 'validation_agreement{metric="agreement_pct_7d"}' +check_otel_metric 'validation_agreement{metric="agreements_1h"}' +check_otel_metric 'validation_agreement{metric="missed_1h"}' +check_otel_metric 'validation_agreement{metric="agreements_24h"}' +check_otel_metric 'validation_agreement{metric="missed_24h"}' +check_otel_metric 'validation_agreement{metric="agreements_7d"}' +check_otel_metric 'validation_agreement{metric="missed_7d"}' # --------------------------------------------------------------------------- # Step 11: Summary diff --git a/docker/telemetry/otel-collector-config.grafanacloud.yaml b/docker/telemetry/otel-collector-config.grafanacloud.yaml new file mode 100644 index 0000000000..5fe6957c72 --- /dev/null +++ b/docker/telemetry/otel-collector-config.grafanacloud.yaml @@ -0,0 +1,280 @@ +# OpenTelemetry Collector configuration — Grafana Cloud dual-export variant. +# +# Identical to otel-collector-config.yaml (local Tempo/Prometheus/Loki) but +# ALSO ships all three signals to Grafana Cloud over a single OTLP/HTTP +# endpoint. Local backends are kept so the on-box stack still works as a +# fallback; remove the local exporters from the pipelines below if you want +# cloud-only. +# +# Selecting this config is a RUNTIME choice — no code or image changes: +# Local only (default): +# docker compose -f docker/telemetry/docker-compose.yml up -d +# Local + Grafana Cloud: +# docker compose -f docker/telemetry/docker-compose.yml \ +# -f docker/telemetry/docker-compose.grafanacloud.yaml up -d +# +# The override file mounts THIS config over the collector's config path and +# injects the three required secrets as environment variables: +# GRAFANA_CLOUD_OTLP_ENDPOINT e.g. https://otlp-gateway-.grafana.net/otlp +# GRAFANA_CLOUD_INSTANCE_ID numeric Grafana Cloud stack/instance id +# GRAFANA_CLOUD_API_TOKEN a Cloud Access Policy token with metrics/traces/logs:write +# Find all three under Grafana Cloud -> Connections -> "OpenTelemetry (OTLP)". + +extensions: + health_check: + endpoint: 0.0.0.0:13133 + # Basic-auth for the Grafana Cloud OTLP gateway: username = instance id, + # password = API token. Values come from the container environment so no + # secret is committed to the repo. + basicauth/grafanacloud: + client_auth: + username: ${env:GRAFANA_CLOUD_INSTANCE_ID} + password: ${env:GRAFANA_CLOUD_API_TOKEN} + +receivers: + otlp: + protocols: + grpc: + endpoint: 0.0.0.0:4317 + http: + endpoint: 0.0.0.0:4318 + filelog: + include: + - /var/log/xrpld/*/debug.log + operators: + - type: regex_parser + regex: '^(?P\S+\s+\S+)\s+\S+\s+(?:(?P\S+):)?(?P\S+)\s+(?:trace_id=(?P[a-f0-9]+)\s+span_id=(?P[a-f0-9]+)\s+)?(?P.*)$' + timestamp: + parse_from: attributes.timestamp + layout: "%Y-%b-%d %H:%M:%S.%f" + location: UTC + +processors: + batch: + timeout: 1s + send_batch_size: 100 + # Box-only (uncommitted): probabilistic 0.5% tail sampling on the + # trace-storage branch only. spanmetrics-derived metrics see 100% of spans + # (they run on a separate branch) so RED remain exact; Tempo/Cloud stores + # ~1/200 of traces to keep long-running node cost bounded. + tail_sampling: + decision_wait: 10s + num_traces: 50000 + policies: + - name: half-percent + type: probabilistic + probabilistic: + sampling_percentage: 0.5 + resource/logs: + attributes: + - key: service.name + value: xrpld + action: upsert + - key: job + value: xrpld + action: upsert + # Deployment-tier tagging. Each collector serves ONE environment and ONE + # network, so it stamps both onto every signal it forwards to Grafana + # Cloud, letting one cloud stack hold data from many collectors and + # filter by tier. + # - deployment.environment: the collector IS the environment (local, ci, + # test, prod), so it is authoritative -> upsert (overwrite). + # - xrpl.network.type: the xrpld node knows its own chain and already + # stamps this, so the collector only fills it when absent -> insert. + # This keeps a node's real network (e.g. a local node on mainnet) + # from being overwritten by a collector's default. + # Replace the placeholder values per collector; see the "Deployment + # Tiers" section of docs/telemetry-runbook.md. + resource/tier: + attributes: + - key: deployment.environment + value: local + action: upsert + - key: xrpl.network.type + value: mainnet + action: insert + # Strip SDK-injected resource attributes (telemetry.sdk.language/name/version). + # The OpenTelemetry SDK auto-adds these to every Resource; they carry no + # operational value, so drop them for every signal on every backend. + resource/stripsdk: + attributes: + - key: telemetry.sdk.language + action: delete + - key: telemetry.sdk.name + action: delete + - key: telemetry.sdk.version + action: delete + # Grafana Cloud ingests metrics via OTLP (no Prometheus scrape), so the + # tier/instance resource attributes never become series labels the way the + # local prometheus exporter promotes them (resource_to_telemetry_conversion + # is Prometheus-exporter-only). Copy them onto datapoint labels so the + # dashboards' $node / $deployment_environment / $xrpl_network_type filters + # resolve on Cloud exactly as they do locally. + # + # NOTE: service.name is intentionally NOT copied here. Cloud's OTLP ingest + # already promotes service.name to the `service_name` label, and adding a + # datapoint attribute with the same name causes Mimir to concatenate both + # values as `xrpld;xrpld`. service.instance.id does not exhibit the same + # doubling, so the copy for $node is kept. + transform/cloudlabels: + metric_statements: + - context: datapoint + statements: + - set(attributes["service_instance_id"], resource.attributes["service.instance.id"]) + - set(attributes["deployment_environment"], resource.attributes["deployment.environment"]) + - set(attributes["xrpl_network_type"], resource.attributes["xrpl.network.type"]) + +connectors: + spanmetrics: + namespace: "span" + # Emit span-derived metrics every 15s (default is 60s). Grafana Cloud + # ingests these as one datapoint per flush, and dashboard rate() panels + # use [$__rate_interval], which at short ranges (<=1h) can be shorter than + # the flush interval. A 60s flush left <2 samples per rate window on Cloud, + # so those panels rendered "No data". 15s keeps >=2 samples per window at + # every dashboard range, at the cost of ~4x span-metric datapoints to Cloud. + metrics_flush_interval: 15s + # Resource attributes that define the resource-metrics grouping key. + # All resource attributes propagate to the output metrics regardless; + # label promotion is handled by resource_to_telemetry_conversion on the + # prometheus exporter. Listing the per-node id and tier keys keeps + # series from distinct nodes/tiers grouped separately. + resource_metrics_key_attributes: + - service.instance.id + - deployment.environment + - xrpl.network.type + histogram: + # Pin unit=ms so the metric stays span_duration_milliseconds_* and le + # labels stay in ms even if a future collector flips the default to + # seconds (connector.spanmetrics.useSecondAsDefaultMetricsUnit gate). + unit: ms + explicit: + # Buckets MUST stay strictly ascending (the connector binary-searches + # them and silently misbuckets otherwise). Sub-MILLISECOND boundaries + # (0.01-0.5ms) exist because most xrpld spans are far below 1ms -- + # tx.preflight means ~0.012ms, tx.preclaim ~0.15ms -- so a 1ms floor + # put >99.99% of samples in the first bucket and pinned every p95/p99 + # at a constant 0.95ms. Sub-second boundaries cover + # RPC/tx/ledger spans; the 2s-4s boundaries resolve second-scale + # consensus spans (consensus.round ~3.9s, consensus.establish ~1.9s) + # that used to pile into a single 1s-5s bucket; 10s/30s give the + # ledger.acquire tail (~17% exceeds 5s during catch-up) a measurable + # home so its p95/p99 stop reading as +Inf. + buckets: + [ + 0.01ms, + 0.05ms, + 0.1ms, + 0.25ms, + 0.5ms, + 1ms, + 5ms, + 10ms, + 25ms, + 50ms, + 100ms, + 250ms, + 500ms, + 1s, + 2s, + 3s, + 4s, + 5s, + 10s, + 30s, + ] + dimensions: + - name: command + - name: rpc_status + - name: consensus_mode + # Only the boolean close_time_correct is promoted, never the close_time + # value itself: close_time is a monotonic NetClock timestamp, so a + # dimension on it would mint a new metric series every ledger (unbounded + # cardinality). The close-time value is exposed as the server_info + # last_close_time gauge instead (for last-close age); the close interval + # comes from the ledgers_closed_total counter. + - name: close_time_correct + - name: consensus_state + - name: local + - name: suppressed + - name: proposal_trusted + - name: validation_trusted + - name: tx_type + - name: ter_result + - name: stage + - name: txq_status + - name: load_type + - name: is_batch + - name: mode_new + - name: consensus_stalled + - name: consensus_phase + - name: consensus_result + - name: method + - name: grpc_role + - name: grpc_status + - name: outcome + - name: acquire_reason + +exporters: + otlp/tempo: + endpoint: tempo:4317 + tls: + insecure: true + otlphttp/loki: + endpoint: http://loki:3100/otlp + prometheus: + endpoint: 0.0.0.0:8889 + # Promote resource attributes (deployment.environment, xrpl.network.type, + # service.name, service.instance.id) to Prometheus labels so the local + # scrape surface can be filtered by tier. (Grafana Cloud ingests the + # attributes natively via OTLP, so this only affects the local export.) + resource_to_telemetry_conversion: + enabled: true + # Single OTLP/HTTP exporter to Grafana Cloud. The gateway fans the three + # signals out to hosted Tempo (traces), Mimir/Prometheus (metrics), and + # Loki (logs). Retry + queue guard against transient gateway errors. + otlphttp/grafanacloud: + endpoint: ${env:GRAFANA_CLOUD_OTLP_ENDPOINT} + auth: + authenticator: basicauth/grafanacloud + retry_on_failure: + enabled: true + sending_queue: + enabled: true + +service: + extensions: [health_check, basicauth/grafanacloud] + pipelines: + # Each pipeline keeps its local exporter(s) AND adds Grafana Cloud. + # For cloud-only, drop debug/otlp/tempo, prometheus, and otlphttp/loki + # from the respective exporter lists. + # 100% of spans feed the spanmetrics connector so span-derived RED + # metrics stay exact. No tail sampling on this branch. + traces/metrics: + receivers: [otlp] + processors: [resource/tier, resource/stripsdk, batch] + exporters: [spanmetrics] + # Trace-STORAGE branch: 0.5% probabilistic tail sampling before Tempo + # and Grafana Cloud, so stored trace volume is ~1/200 of ingested spans. + traces/store: + receivers: [otlp] + processors: [tail_sampling, resource/tier, resource/stripsdk, batch] + exporters: [otlp/tempo, otlphttp/grafanacloud] + # The local Prometheus scrape promotes tier/instance resource attrs to + # labels via resource_to_telemetry_conversion; Grafana Cloud (OTLP) does + # not, so it runs a separate pipeline that copies them onto datapoint + # labels via transform/cloudlabels. Splitting avoids the local scrape and + # the transform both writing a service_instance_id label on the same series. + metrics/local: + receivers: [otlp, spanmetrics] + processors: [resource/tier, resource/stripsdk, batch] + exporters: [prometheus] + metrics/cloud: + receivers: [otlp, spanmetrics] + processors: + [resource/tier, resource/stripsdk, transform/cloudlabels, batch] + exporters: [otlphttp/grafanacloud] + logs: + receivers: [filelog] + processors: [resource/logs, resource/tier, resource/stripsdk, batch] + exporters: [otlphttp/loki, otlphttp/grafanacloud] diff --git a/docker/telemetry/otel-collector-config.yaml b/docker/telemetry/otel-collector-config.yaml index 6200318b56..2188417ad5 100644 --- a/docker/telemetry/otel-collector-config.yaml +++ b/docker/telemetry/otel-collector-config.yaml @@ -123,12 +123,55 @@ connectors: - deployment.environment - xrpl.network.type histogram: + # Pin unit=ms so the metric stays span_duration_milliseconds_* and le + # labels stay in ms even if a future collector flips the default to + # seconds (connector.spanmetrics.useSecondAsDefaultMetricsUnit gate). + unit: ms explicit: - buckets: [1ms, 5ms, 10ms, 25ms, 50ms, 100ms, 250ms, 500ms, 1s, 5s] + # Buckets MUST stay strictly ascending (the connector binary-searches + # them and silently misbuckets otherwise). Sub-MILLISECOND boundaries + # (0.01-0.5ms) exist because most xrpld spans are far below 1ms -- + # tx.preflight means ~0.012ms, tx.preclaim ~0.15ms -- so a 1ms floor + # put >99.99% of samples in the first bucket and pinned every p95/p99 + # at a constant 0.95ms. Sub-second boundaries cover + # RPC/tx/ledger spans; the 2s-4s boundaries resolve second-scale + # consensus spans (consensus.round ~3.9s, consensus.establish ~1.9s) + # that used to pile into a single 1s-5s bucket; 10s/30s give the + # ledger.acquire tail (~17% exceeds 5s during catch-up) a measurable + # home so its p95/p99 stop reading as +Inf. + buckets: + [ + 0.01ms, + 0.05ms, + 0.1ms, + 0.25ms, + 0.5ms, + 1ms, + 5ms, + 10ms, + 25ms, + 50ms, + 100ms, + 250ms, + 500ms, + 1s, + 2s, + 3s, + 4s, + 5s, + 10s, + 30s, + ] dimensions: - name: command - name: rpc_status - name: consensus_mode + # Only the boolean close_time_correct is promoted, never the close_time + # value itself: close_time is a monotonic NetClock timestamp, so a + # dimension on it would mint a new metric series every ledger (unbounded + # cardinality). The close-time value is exposed as the server_info + # last_close_time gauge instead (for last-close age); the close interval + # comes from the ledgers_closed_total counter. - name: close_time_correct - name: consensus_state - name: local diff --git a/docker/telemetry/xrpld-telemetry-mainnet.cfg b/docker/telemetry/xrpld-telemetry-mainnet.cfg index b4ba0d6f1a..afca57e4fa 100644 --- a/docker/telemetry/xrpld-telemetry-mainnet.cfg +++ b/docker/telemetry/xrpld-telemetry-mainnet.cfg @@ -101,7 +101,7 @@ docker/telemetry/data data/logs/mainnet/debug.log [rpc_startup] -{ "command": "log_level", "severity": "debug" } +{ "command": "log_level", "severity": "warning" } # --- SSL -------------------------------------------------------------------- @@ -115,7 +115,7 @@ server=otel endpoint=http://localhost:4318/v1/metrics prefix=xrpld # Sets the OTel service.instance.id resource attribute, which Prometheus -# exposes as the `exported_instance` label. Dashboards filter on it via the +# exposes as the `service_instance_id` label. Dashboards filter on it via the # $node template variable, so without this every insight-backed panel is # empty. Matches [telemetry] service_instance_id for a single node identity. service_instance_id=xrpld-mainnet diff --git a/docker/telemetry/xrpld-telemetry.cfg b/docker/telemetry/xrpld-telemetry.cfg index 878b20624a..bd7454e598 100644 --- a/docker/telemetry/xrpld-telemetry.cfg +++ b/docker/telemetry/xrpld-telemetry.cfg @@ -113,7 +113,7 @@ server=otel endpoint=http://localhost:4318/v1/metrics prefix=xrpld # Sets the OTel service.instance.id resource attribute, which Prometheus -# exposes as the `exported_instance` label. Dashboards filter on it via the +# exposes as the `service_instance_id` label. Dashboards filter on it via the # $node template variable, so without this every insight-backed panel is # empty. Matches [telemetry] service_instance_id for a single node identity. service_instance_id=xrpld-devnet diff --git a/docs/telemetry-glossary.md b/docs/telemetry-glossary.md new file mode 100644 index 0000000000..62e0e42160 --- /dev/null +++ b/docs/telemetry-glossary.md @@ -0,0 +1,915 @@ +# Telemetry Glossary + +Plain-language definitions of the XRP Ledger and `xrpld` terms used in the +Grafana dashboard panel descriptions. Each Grafana panel already carries a short +**Keywords** gloss inline; this page is the deeper reference for going further, +and most terms link out to the canonical [xrpl.org](https://xrpl.org/docs) +documentation. + +> **Related docs**: +> [docs/telemetry-runbook.md](./telemetry-runbook.md) (operator runbook). + + + +## Contents + +- [Ledger Lifecycle](#cat-ledger-lifecycle) +- [Consensus](#cat-consensus) +- [Transaction Pipeline](#cat-transaction-pipeline) +- [Fees & Queue](#cat-fees-queue) +- [Job Queue](#cat-job-queue) +- [Node State & Sync](#cat-node-state-sync) +- [Peer & Overlay Networking](#cat-peer-overlay-networking) +- [Storage Internals](#cat-storage-internals) +- [Validator Health](#cat-validator-health) +- [RPC & Pathfinding](#cat-rpc-pathfinding) + + + +## Ledger Lifecycle + + + +### Ledger build + +Building a ledger means applying the agreed transaction set, in canonical order, onto the previous ledger to produce the new closed ledger and its hash. Build time is a large component of the overall close time. + +**Scope:** per node — measured on and specific to this individual server. + +**See also:** [Ledger build on xrpl.org](https://xrpl.org/docs/concepts/ledgers/open-closed-validated-ledgers) + + + +### Ledger close + +The XRP Ledger never converts an open ledger into a closed one; instead the server discards the open ledger and builds a new closed ledger by applying the consensus-agreed transaction set (in canonical order) on top of the previous closed ledger. Consensus triggers the close; the close completes when the new ledger is built. + +**Scope:** network event — a network-wide consensus process; this metric is one node's view of it. + +**See also:** [Ledger close on xrpl.org](https://xrpl.org/docs/concepts/ledgers/open-closed-validated-ledgers) + + + +### Ledger close interval + +The XRP Ledger closes a new ledger at a roughly steady cadence (about every 3-5 seconds on Mainnet). Close times are rounded to a shared resolution so validators can agree on them. A node that closes far slower or faster than the network cadence is not keeping up or is misbehaving. + +**Scope:** network-wide — a protocol-shared value, the same across all nodes. + +**See also:** [Ledger close interval on xrpl.org](https://xrpl.org/docs/concepts/ledgers/ledger-close-times) + + + +### Ledger index + +The ledger index (or ledger sequence) is the position of a ledger version in the chain, incremented by one for each new ledger. The current/open ledger index is one or two ahead of the latest validated sequence on a synced node. + +**Scope:** network-wide — a protocol-shared value, the same across all nodes. + +**See also:** [Ledger index on xrpl.org](https://xrpl.org/docs/references/protocol/data-types/basic-data-types#ledger-index) + + + +### Ledger store + +After a ledger is built and validated, the node persists it into its ledger history (the object store). The store rate should track the build rate on a healthy, in-sync node. + +**Scope:** per node — measured on and specific to this individual server. + + + +### Ledger validation + +Validation is the stage after transaction-set agreement: each server independently computes the ledger from the agreed set, then compares results. When enough trusted validators agree on the same ledger, it is declared validated (final and immutable). A validating node issues one validation per ledger it fully validates. + +**Scope:** network event — a network-wide consensus process; this metric is one node's view of it. + +**See also:** [Ledger validation on xrpl.org](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure#validation) + + + +### Open ledger + +A server has exactly one open ledger: a temporary workspace where it provisionally applies transactions in the order received. Its results are tentative and can differ from the final validated result, because the closed ledger applies transactions in canonical order instead. + +**Scope:** per node — measured on and specific to this individual server. + +**See also:** [Open ledger on xrpl.org](https://xrpl.org/docs/concepts/ledgers/open-closed-validated-ledgers) + + + +### Published ledger + +After a ledger is validated the node publishes it to internal subscribers and clients. The published ledger normally tracks the validated ledger closely; a growing gap means the publish pipeline is backing up and subscribers may see stale data. + +**Scope:** per node — measured on and specific to this individual server. + + + +### Transaction apply phase + +During a ledger close the server executes each transaction in the agreed set, in canonical order, updating ledger state. This apply phase (xrpld's doAccept path) is typically the largest single component of ledger-build time and scales with the number and cost of transactions in the ledger. + +**Scope:** per node — measured on and specific to this individual server. + +**See also:** [Transaction apply phase on xrpl.org](https://xrpl.org/docs/concepts/ledgers/open-closed-validated-ledgers) + + + +### Validated ledger + +A validated ledger is one that a quorum of trusted validators has agreed on. It is immutable and forms part of the permanent ledger history. Each ledger index has exactly one validated ledger. + +**Scope:** network-wide — a protocol-shared value, the same across all nodes. + +**See also:** [Validated ledger on xrpl.org](https://xrpl.org/docs/concepts/ledgers/open-closed-validated-ledgers) + + + +## Consensus + + + +### Clock drift + +Because each validator stamps its own observed close time, differences between validator clocks (drift) spread the proposed close times, forcing coarser close-time resolution and more distinct positions. + +**Scope:** network event — a network-wide consensus process; this metric is one node's view of it. + +**See also:** [Clock drift on xrpl.org](https://xrpl.org/docs/concepts/ledgers/ledger-close-times) + + + +### Close time + +Each validator proposes the wall-clock time it saw the ledger close; validators then agree on a common close time, rounded to a shared resolution so their ledgers match. Disagreement on close time forces the rounding resolution coarser and can cause extra rounds. + +**Scope:** network event — a network-wide consensus process; this metric is one node's view of it. + +**See also:** [Close time on xrpl.org](https://xrpl.org/docs/concepts/ledgers/ledger-close-times) + + + +### Close-time resolution + +Close times are rounded to a shared resolution (a number of seconds) so validators can agree on a single value. When validators disagree on close time the resolution moves coarser (up toward 120s); when they agree tightly it moves finer. Repeated coarsening signals persistent close-time disagreement. + +**Scope:** network event — a network-wide consensus process; this metric is one node's view of it. + +**See also:** [Close-time resolution on xrpl.org](https://xrpl.org/docs/concepts/ledgers/ledger-close-times) + + + +### Consensus + +XRP Ledger consensus is an iterative agreement protocol: each server listens to its trusted validators and, when a supermajority agree on the same transaction set and close time, declares consensus and builds the ledger. If they disagree, validators revise proposals over successive rounds until they converge. + +**Scope:** network event — a network-wide consensus process; this metric is one node's view of it. + +**See also:** [Consensus on xrpl.org](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) + + + +### Consensus mode + +Consensus mode reflects how a node is participating: Proposing (a validator advancing its own proposal), Observing (following without proposing), Wrong Ledger (working from a ledger the network disagrees with), or Switched Ledger (just changed to match the network). Sustained time in Wrong/Switched Ledger indicates the node is out of sync or flapping. + +**Scope:** per node — measured on and specific to this individual server. + +**See also:** [Consensus mode on xrpl.org](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) + + + +### Consensus outcome + +The result of a consensus round. Agreed is the healthy outcome. Moved On means the node proceeded without full agreement; Expired means the round timed out; No Consensus means agreement was not reached. A growing share of non-Agreed outcomes signals network stress or connectivity loss. + +**Scope:** network event — a network-wide consensus process; this metric is one node's view of it. + +**See also:** [Consensus outcome on xrpl.org](https://xrpl.org/docs/concepts/consensus-protocol/consensus-principles-and-rules) + + + +### Consensus round + +A consensus round is one iteration in which validators relay and revise proposals. Multiple rounds (the establish count) may be needed within a single ledger before validators converge. Longer or more numerous rounds indicate disagreement, load, or poor connectivity. + +**Scope:** network event — a network-wide consensus process; this metric is one node's view of it. + +**See also:** [Consensus round on xrpl.org](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) + + + +### Consensus stall + +A stalled condition is flagged when consensus health checks detect that rounds are not progressing. A nonzero stall rate is an early warning that can precede ledger stalls or forks, surfacing before validated-ledger-age alarms fire. + +**Scope:** per node — measured on and specific to this individual server. + +**See also:** [Consensus stall on xrpl.org](https://xrpl.org/docs/concepts/consensus-protocol/consensus-principles-and-rules) + + + +### Convergence time + +How long consensus took to converge on the agreed transaction set and close time, typically a few seconds. A rising convergence time indicates the network is taking longer to agree, often from load or connectivity problems. + +**Scope:** network event — a network-wide consensus process; this metric is one node's view of it. + +**See also:** [Convergence time on xrpl.org](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) + + + +### Distinct positions + +A count of how many different close-time positions validators held in a round. Weight on a single distinct position means everyone agreed; weight on two or more means proposals split, indicating clock drift or latency spread across the validator set. + +**Scope:** network event — a network-wide consensus process; this metric is one node's view of it. + +**See also:** [Distinct positions on xrpl.org](https://xrpl.org/docs/concepts/ledgers/ledger-close-times) + + + +### Establish phase + +The establish phase is the part of consensus where validators exchange and revise proposals until they converge. The establish (iteration) count per ledger is normally low (a few iterations); a growing share of ledgers needing many iterations signals disagreement or network stress. + +**Scope:** network event — a network-wide consensus process; this metric is one node's view of it. + +**See also:** [Establish phase on xrpl.org](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) + + + +### Fork + +A fork occurs when parts of the network validate different ledger chains. Sustained history mismatches or nodes stuck on the Wrong Ledger are fork indicators; the network is designed to avoid forks by requiring a trusted-validator quorum. + +**Scope:** network-wide — a protocol-shared value, the same across all nodes. + +**See also:** [Fork on xrpl.org](https://xrpl.org/docs/concepts/consensus-protocol) + + + +### Ledger history mismatch + +A mismatch is recorded when the ledger this node built does not hash-match the ledger the network validated. Any sustained rate indicates consensus divergence or database corruption and warrants immediate investigation; the reason label distinguishes close-time, sync-drift, and transaction-processing causes. + +**Scope:** per node — measured on and specific to this individual server. + + + +### Position update + +Each round a node tallies disputed transactions and updates its own proposed position to move toward its trusted peers. Sustained high position-update durations point to heavy dispute resolution or slow convergence. + +**Scope:** per node — measured on and specific to this individual server. + +**See also:** [Position update on xrpl.org](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) + + + +### Proposal + +During consensus each validator broadcasts a proposal: the set of candidate transactions it thinks should be in the next ledger. Validators revise proposals over rounds to match their trusted peers until a supermajority agree. + +**Scope:** network event — a network-wide consensus process; this metric is one node's view of it. + +**See also:** [Proposal on xrpl.org](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) + + + +### Proposers + +The number of distinct validators that proposed in the last consensus round. A falling proposer count (alongside rising convergence time) signals degrading consensus conditions, such as lost validator connectivity. + +**Scope:** network event — a network-wide consensus process; this metric is one node's view of it. + +**See also:** [Proposers on xrpl.org](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure) + + + +## Transaction Pipeline + + + +### Apply pipeline stages + +A transaction is processed in stages: preflight validates it without ledger state (signature, format), preclaim checks it against current ledger state, and apply executes it and commits state changes. A failure spike concentrated in one stage pinpoints where transactions are being rejected. + +**Scope:** per node — measured on and specific to this individual server. + +**See also:** [Apply pipeline stages on xrpl.org](https://xrpl.org/docs/concepts/transactions) + + + +### Direct apply vs enqueue + +When a transaction arrives, the node either applies it directly to the open ledger (if it meets the open-ledger cost) or enqueues it for a future ledger. The bypass ratio is the share applied directly; it falls as congestion pushes more transactions into the queue. + +**Scope:** per node — measured on and specific to this individual server. + +**See also:** [Direct apply vs enqueue on xrpl.org](https://xrpl.org/docs/concepts/transactions/transaction-queue) + + + +### Local vs relayed transactions + +Transactions either originate from a client submitting to this node (local) or are relayed from peers. Most traffic on a network node is peer-relayed; local dominates on a submission node. An unexpected surge in local submissions can indicate a client flooding the node. + +**Scope:** per node — measured on and specific to this individual server. + + + +### Queue accept (drain) + +When a ledger closes, the node drains eligible queued transactions into it. The accept/applied ratio is the share of drained transactions that were included versus removed on failure. A healthy drain applies most of them; a low ratio means accepts are mostly failing. + +**Scope:** per node — measured on and specific to this individual server. + +**See also:** [Queue accept (drain) on xrpl.org](https://xrpl.org/docs/concepts/transactions/transaction-queue) + + + +### Transaction result codes + +Every transaction returns a result code grouped by prefix: tes (success), tec (failed but cost claimed, included in ledger), tef (cannot apply to this ledger or a later one), tem (malformed, cannot succeed in any ledger), ter (retry later), tel (local error). A steady background of tef/tec results is normal; a surge of one code for one type indicates a systemic issue or abusive submissions. + +**Scope:** network-wide — a protocol-shared value, the same across all nodes. + +**See also:** [Transaction result codes on xrpl.org](https://xrpl.org/docs/references/protocol/transactions/transaction-results) + + + +### Transaction suppression + +The same transaction reaches a node from many peers; the node suppresses (discards) copies it has already seen so they are not reprocessed. A large suppressed share is normal on a well-connected node; a collapse in suppression means duplicate filtering is failing. + +**Scope:** per node — measured on and specific to this individual server. + + + +### Transaction type + +Each transaction has a type that determines its logic and cost, such as Payment, OfferCreate, TrustSet, or the AMM and NFToken families. Panels break down rate, latency, and failures by type; a single type spiking far above baseline can indicate a spam campaign of that type. + +**Scope:** network-wide — a protocol-shared value, the same across all nodes. + +**See also:** [Transaction type on xrpl.org](https://xrpl.org/docs/references/protocol/transactions/types) + + + +### Transactor + +A transactor is the code that applies a single transaction of a given type, running its type-specific checks and state changes. The transactor stage is the innermost apply step; its result is a transaction result code such as tesSUCCESS (the tes success class). + +**Scope:** per node — measured on and specific to this individual server. + +**See also:** [Transactor on xrpl.org](https://xrpl.org/docs/references/protocol/transactions/transaction-results) + + + +## Fees & Queue + + + +### Base fee + +The base fee is the transaction cost a reference (cheapest) transaction must destroy under minimum load, currently 10 drops on Mainnet. The actual required fee is the base fee scaled by the load factor and, when the open ledger is busy, by fee escalation. + +**Scope:** network-wide — a protocol-shared value, the same across all nodes. + +**See also:** [Base fee on xrpl.org](https://xrpl.org/docs/concepts/transactions/transaction-cost) + + + +### drops + +A drop is the smallest denomination of XRP: 1 XRP = 1,000,000 drops. Fees, reserves, and costs are often reported in drops. + +**Scope:** network-wide — a protocol-shared value, the same across all nodes. + +**See also:** [drops on xrpl.org](https://xrpl.org/docs/references/protocol/data-types/basic-data-types#specifying-currency-amounts) + + + +### Fee escalation + +Once the open ledger holds more than its soft target number of transactions, the cost to add further transactions rises exponentially (fee escalation). Transactions that cannot pay the escalated cost are queued instead. A large gap of the open-ledger level above the reference level means escalation is active. + +**Scope:** per node — measured on and specific to this individual server. + +**See also:** [Fee escalation on xrpl.org](https://xrpl.org/docs/concepts/transactions/transaction-cost#open-ledger-cost) + + + +### Fee levels + +Fee levels express the transaction cost relative to a transaction's own minimum, so they compare across transaction types. Key levels are reference (baseline, 256), minimum (to queue), median (of the last ledger), and open-ledger (to enter the current open ledger). The open-ledger level spiking far above reference is the hallmark of congestion. + +**Scope:** per node — measured on and specific to this individual server. + +**See also:** [Fee levels on xrpl.org](https://xrpl.org/docs/concepts/transactions/transaction-cost#fee-levels) + + + +### In-ledger vs target count + +The node sets a soft target for how many transactions belong in a ledger, based on the previous ledger. While the in-ledger count stays at or below target, the open-ledger cost is minimal; exceeding it triggers exponential fee escalation. + +**Scope:** per node — measured on and specific to this individual server. + +**See also:** [In-ledger vs target count on xrpl.org](https://xrpl.org/docs/concepts/transactions/transaction-cost#open-ledger-cost) + + + +### Load factor + +The load factor is a multiplier applied to the base transaction cost; 1.0 means no load. It combines local server load, network load, and cluster load. A rising factor means the node is charging premium fees due to congestion or overload; the components identify where the pressure originates. + +**Scope:** per node — measured on and specific to this individual server. + +**See also:** [Load factor on xrpl.org](https://xrpl.org/docs/concepts/transactions/transaction-cost#local-load-cost) + + + +### Queue admission rejection + +When the queue is at capacity or a transaction is unlikely to be included, the node refuses it entry (a drop), applying backpressure. A burst of queue_full rejections, distinct from expiry, means the node is being flooded faster than it can drain. + +**Scope:** per node — measured on and specific to this individual server. + +**See also:** [Queue admission rejection on xrpl.org](https://xrpl.org/docs/concepts/transactions/transaction-queue) + + + +### Queue expiry / abandonment + +A queued transaction carrying a LastLedgerSequence is dropped once that deadline passes without inclusion. A sustained expiry rate is a demand-frustration signal: submitters under-bid the escalating fee and their transactions timed out, often coinciding with spam. + +**Scope:** per node — measured on and specific to this individual server. + +**See also:** [Queue expiry / abandonment on xrpl.org](https://xrpl.org/docs/concepts/transactions/reliable-transaction-submission) + + + +### Reserve (base & owner) + +Reserves protect the ledger from spam by requiring accounts to hold XRP. The base reserve is the minimum per account; the owner (incremental) reserve adds a further requirement per object the account owns (offers, trust lines, escrows, etc.). Both are set by validator fee voting and reported in drops. + +**Scope:** network-wide — a protocol-shared value, the same across all nodes. + +**See also:** [Reserve (base & owner) on xrpl.org](https://xrpl.org/docs/concepts/accounts/reserves) + + + +### Transaction cost + +Every transaction must destroy a small amount of XRP (the transaction cost) to be relayed and included, which deters spam. The cost is the base fee scaled by the current load factor, and rises further under fee escalation when the open ledger is congested. + +**Scope:** network-wide — a protocol-shared value, the same across all nodes. + +**See also:** [Transaction cost on xrpl.org](https://xrpl.org/docs/concepts/transactions/transaction-cost) + + + +### Transaction queue (TxQ) + +The transaction queue (TxQ) holds transactions that pay enough for local relay but not the current open-ledger cost, so the node can include them in a future ledger instead of discarding them. Depth pinned at capacity for sustained periods signals demand exceeding throughput or a fee-spam burst. + +**Scope:** per node — measured on and specific to this individual server. + +**See also:** [Transaction queue (TxQ) on xrpl.org](https://xrpl.org/docs/concepts/transactions/transaction-queue) + + + +## Job Queue + + + +### Concurrency limit + +Each job type declares how many of its jobs may run at the same time. Sync-critical types are deliberately tight so one kind of work cannot monopolize the worker pool. A type sitting at its limit cannot start more work even when worker threads are idle, which makes the limit a distinct kind of bottleneck from CPU or disk. + +**Scope:** per node — measured on and specific to this individual server. + + + +### Deferred job + +When a job arrives for a type that is already at its concurrency limit, the queue holds it back rather than refusing it. Adding a job never fails for queue pressure, so a deferred count is the earliest signal that a type is oversubscribed — latency only shows the problem afterwards. Each completing job releases one deferred job, so a count that stays high means arrivals are outpacing completions. + +**Scope:** per node — measured on and specific to this individual server. + + + +### Handler label + +Several call sites can enqueue work under the same job type, so job type alone cannot say which one caused a latency spike. The handler label carries the name of the call site that enqueued the job. Names are kept only when they are letters-only; anything else — including names that embed a ledger sequence number — folds into a shared `other` bucket, which keeps the number of series bounded. A reading under `other` therefore mixes several callers and never identifies one. + +**Scope:** per node — measured on and specific to this individual server. + + + +### Job queue / job type + +The job queue is the worker-thread pool that runs xrpld's background work. Every unit of work is enqueued under a named job type — serving a peer's ledger request, absorbing inbound ledger data, updating payment paths, and so on — and each type is accounted separately: waiting, running, and deferred. Types that carry sync-critical traffic are the ones worth watching, because a backlog there translates directly into the node falling behind. + +**Scope:** per node — measured on and specific to this individual server. + + + +## Node State & Sync + + + +### Back-fill / catch-up + +When a node is missing ledgers (at startup, after an outage, or to extend history) it back-fills by fetching them from peers. Elevated back-fill activity is normal while catching up and should fall to near zero once history is complete and the node is synced. + +**Scope:** per node — measured on and specific to this individual server. + + + +### Complete ledger ranges + +A node stores ledger history as one or more contiguous ranges. One continuous range means an unbroken history; many fragmented ranges indicate gaps from missed or failed fetches that the node is still back-filling. + +**Scope:** per node — measured on and specific to this individual server. + + + +### Historical fetch rate + +The rate at which the node fetches older ledgers to extend or repair its stored history. Elevated while back-filling; near zero once history is complete. + +**Scope:** per node — measured on and specific to this individual server. + + + +### Ledger acquire (inbound fetch) + +Acquiring a ledger means requesting it and its contents from peers when the node lacks it. Acquire outcomes split into complete and failed; a rising failed rate means the node cannot fetch needed ledgers from its peers. + +**Scope:** per node — measured on and specific to this individual server. + + + +### Operating mode / server state + +The server state describes how fully the node is participating, in ascending order: disconnected, connected, syncing, tracking, full (caught up), and for validators validating and proposing. A healthy non-validator sits in Full; frequent transitions out of Full indicate instability. + +**Scope:** per node — measured on and specific to this individual server. + +**See also:** [Operating mode / server state on xrpl.org](https://xrpl.org/docs/references/http-websocket-apis/api-conventions/xrpld-server-states) + + + +## Peer & Overlay Networking + + + +### Cluster + +A cluster is a set of servers run by the same operator that trust each other, exchanging load and status directly and skipping some redundant checks. Cluster overhead is routine; sustained high cluster overhead suggests frequent cluster-state churn. + +**Scope:** cluster-wide — shared across a co-operated cluster of nodes run by one operator. + +**See also:** [Cluster on xrpl.org](https://xrpl.org/docs/concepts/networks-and-servers/clustering) + + + +### Fetch-pack + +A fetch-pack is a bulk transfer of ledger data used to accelerate catch-up across a range of ledgers. Fetch-pack traffic rises sharply while catching up and is near zero when fully synced; continuous fetch-pack traffic means the node never fully catches up. + +**Scope:** per node — measured on and specific to this individual server. + + + +### GetObject / object fetch + +GetObject messages fetch individual pieces of ledger data from peers, broken down by object type: ledger headers, individual transactions, transaction-tree nodes, and state-tree nodes. Many messages carrying few bytes means small piecemeal fetches; few large messages means batch transfers. + +**Scope:** per node — measured on and specific to this individual server. + + + +### Have / requested transactions + +Have-transaction messages advertise that a peer holds particular transactions; requested-transaction messages ask for them. Comparing requested versus have gauges how well transactions are propagating; requested far exceeding have means peers are behind on propagation. + +**Scope:** per node — measured on and specific to this individual server. + + + +### Insane / diverged peers + +Diverged (insane) peers are connected peers whose reported ledger state does not match the network's. Zero is healthy; a persistent nonzero count can indicate peers on a fork or misbehaving peers. + +**Scope:** per node — measured on and specific to this individual server. + + + +### Ledger tree nodes + +A ledger's contents are stored as Merkle trees (SHAMaps): a transaction tree and an account-state tree, each built from tree nodes. Peers fetch individual tree nodes (tx-node, account-state-node) to reconstruct a ledger. Account-state-node traffic dominates during state sync; transaction-set-candidate traffic dominates during consensus catch-up. + +**Scope:** network-wide — a protocol-shared value, the same across all nodes. + + + +### Manifest + +A manifest is a validator's signed statement linking its long-term master key to its current ephemeral signing key, letting it rotate keys without losing trust. Manifest overhead rises around key rotations; sustained high manifest traffic suggests frequent reissue. + +**Scope:** network-wide — a protocol-shared value, the same across all nodes. + + + +### Overlay + +The overlay is xrpld's peer-to-peer messaging layer connecting nodes. All inter-node traffic (transactions, proposals, validations, ledger data, control messages) flows over it, grouped into traffic categories for accounting. + +**Scope:** per node — measured on and specific to this individual server. + +**See also:** [Overlay on xrpl.org](https://xrpl.org/docs/concepts/networks-and-servers/peer-protocol) + + + +### Proof path + +Proof-path request/response messages let a peer verify an individual ledger entry via its Merkle proof, without downloading the entire ledger. Volume rises when peers verify specific state, often during catch-up. + +**Scope:** per node — measured on and specific to this individual server. + + + +### Reduce-relay + +Reduce-relay lowers overlay bandwidth by forwarding proposals/validations through a chosen subset of peers (selected), suppressing the rest, while some older peers have the feature not-enabled. When suppression hides a message a peer needed, it fetches it on demand; a rising missing-tx rate means suppression is too aggressive. + +**Scope:** per node — measured on and specific to this individual server. + + + +### Replay delta + +Replay-delta request/response messages transfer only the state changes between ledgers so a peer can efficiently replay them during catch-up, rather than refetching whole ledgers. Continuous replay traffic means the node is repeatedly replaying rather than staying current. + +**Scope:** per node — measured on and specific to this individual server. + + + +### Resource charge + +The resource manager bills each peer a load cost per request, so expensive requests cost the sender more than cheap ones. For object fetches the charge scales with how many objects were asked for and how many of those were misses, with a surcharge once the request crosses a size band. A running balance above the warning threshold marks the peer as overactive; above the drop threshold the node sheds it. Requests inside the free allowance carry no charge beyond the flat per-message cost. + +**Scope:** per node — measured on and specific to this individual server. + + + +### Resource disconnect + +The resource manager tracks each peer's load and disconnects those exceeding limits. A rising resource-disconnect count is consistent with abusive or misbehaving peers being shed as backpressure; a flat line is healthy. + +**Scope:** per node — measured on and specific to this individual server. + + + +### Set get/share + +Set-get (fetch) and set-share messages exchange transaction-set data between peers as they reconcile which transactions belong in the closing ledger. Some exchange each ledger is normal; high set-get means peers are frequently missing transaction sets. + +**Scope:** per node — measured on and specific to this individual server. + + + +### Squelch + +Squelching is a relay-control mechanism: a node tells peers to stop sending it a particular validator's messages when it already has a good source, reducing redundant forwarding. High suppressed counts mean squelch is saving bandwidth; ignored directives (peers not honoring squelch) should stay low. + +**Scope:** per node — measured on and specific to this individual server. + + + +### Trusted / untrusted / duplicate + +Proposals and validations are trusted if they come from validators on this node's UNL, untrusted otherwise; duplicates are messages the node already received and discarded. High untrusted volume can indicate trusted-list misconfiguration or spam; high duplicates indicate inefficient relay. + +**Scope:** per node — measured on and specific to this individual server. + +**See also:** [Trusted / untrusted / duplicate on xrpl.org](https://xrpl.org/docs/concepts/consensus-protocol/unl) + + + +### Validator list + +Validator lists are the signed, published sets of recommended validators (the basis for a node's UNL). Peers exchange them so nodes stay configured with a current trusted set. Traffic bursts when lists update or new peers connect and is otherwise quiet. + +**Scope:** network-wide — a protocol-shared value, the same across all nodes. + +**See also:** [Validator list on xrpl.org](https://xrpl.org/docs/concepts/consensus-protocol/unl) + + + +## Storage Internals + + + +### Caches (SLE / Ledger / TreeNode / FullBelow / AcceptedLedger) + +xrpld keeps several caches: SLE (ledger entries), Ledger, AcceptedLedger, TreeNode, and the FullBelowCache (subtrees known to be fully present locally). A high hit rate means lookups are served from memory; low or falling hit rates indicate cache thrashing and extra back-end reads. + +**Scope:** per node — measured on and specific to this individual server. + + + +### NodeStore + +The NodeStore is xrpld's content-addressed object database holding all ledger tree nodes, keyed by hash. It is the main on-disk store read during queries and sync and written as new ledgers are stored; NuDB is the default backend. + +**Scope:** per node — measured on and specific to this individual server. + + + +### NodeStore lookup (hit / miss) + +A lookup is one attempt to fetch an object from the NodeStore by its hash. A hit is usually served from an in-memory cache and is cheap; a miss goes to the back-end store and costs a disk seek, so it is far more expensive. The hit/miss mix is therefore the main reason lookup time moves: a rising miss share explains slower lookups without any regression in the storage layer, while slower lookups on a hit-heavy mix point at the storage layer itself. + +**Scope:** per node — measured on and specific to this individual server. + + + +### NuDB + +NuDB is a fast append-only key-value store used as the NodeStore backend. Its on-disk size grows steadily with retained ledger history; the growth slope is the data growth rate. + +**Scope:** per node — measured on and specific to this individual server. + + + +### Read threads / read queue / write load + +The NodeStore serves reads through a pool of read threads (optionally bundling reads) fronted by a read queue, while writes are scored as write load. Read threads pinned at the maximum, or a high read queue, mean read I/O is saturated; high write load means back-end write pressure. + +**Scope:** per node — measured on and specific to this individual server. + + + +## Validator Health + + + +### Amendment blocked + +Amendments are protocol changes activated by validator voting. If the network enables an amendment a node's build does not understand, the node becomes amendment-blocked: it stops processing to avoid diverging, and requires a software upgrade. OK is healthy; BLOCKED means an upgrade is needed. + +**Scope:** per node — measured on and specific to this individual server. + +**See also:** [Amendment blocked on xrpl.org](https://xrpl.org/docs/concepts/networks-and-servers/amendments) + + + +### Ledgers closed rate + +The rate at which this node closes ledgers, roughly 12-20 per minute on Mainnet (one per ~3-5s close). It should match the network's steady cadence; a drop toward zero means the node stopped participating. + +**Scope:** per node — measured on and specific to this individual server. + +**See also:** [Ledgers closed rate on xrpl.org](https://xrpl.org/docs/concepts/ledgers/ledger-close-times) + + + +### UNL (Unique Node List) + +A node's Unique Node List is the set of validators it trusts not to collude. The node reaches consensus by listening to its UNL and declaring a ledger validated when a quorum of them agree. UNL expiry (days left) matters because an expired list leaves the node without a trusted set. + +**Scope:** per node — measured on and specific to this individual server. + +**See also:** [UNL (Unique Node List) on xrpl.org](https://xrpl.org/docs/concepts/consensus-protocol/unl) + + + +### UNL blocked + +UNL-blocked means the node could not establish a valid trusted validator list (for example, all configured lists expired or failed to load), so validator trust cannot be established. OK is healthy; BLOCKED halts safe participation. + +**Scope:** per node — measured on and specific to this individual server. + +**See also:** [UNL blocked on xrpl.org](https://xrpl.org/docs/concepts/consensus-protocol) + + + +### Validation agreement + +Validation agreement is the fraction of recent ledgers where this validator issued a validation matching the consensus outcome (agreed) rather than missing or disagreeing (missed). A sustained dip signals configuration drift, unreliability, or a network partition. + +**Scope:** per node — measured on and specific to this individual server. + +**See also:** [Validation agreement on xrpl.org](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure#validation) + + + +### Validation quorum + +The quorum is the minimum count of trusted-validator validations that must agree before a server declares a ledger validated (by default about 80% of the UNL). It is derived from the active validator list and changes when that list changes. + +**Scope:** network-wide — a protocol-shared value, the same across all nodes. + +**See also:** [Validation quorum on xrpl.org](https://xrpl.org/docs/concepts/consensus-protocol/negative-unl) + + + +### Validations checked vs sent + +Checked validations are those received from other validators and verified by this node (reflecting network validation traffic reaching it); sent validations are those this node issues (roughly one per closed ledger for a validator). + +**Scope:** per node — measured on and specific to this individual server. + +**See also:** [Validations checked vs sent on xrpl.org](https://xrpl.org/docs/concepts/consensus-protocol/consensus-structure#validation) + + + +## RPC & Pathfinding + + + +### Batch vs single RPC + +A single request carries one command; a batch request bundles several. Single requests usually dominate; a batch rate climbing sharply is consistent with bulk automation or amplification attempts. + +**Scope:** per node — measured on and specific to this individual server. + + + +### Clio / reporting client + +Clio is a separate reporting server that offloads historical and API queries from xrpld, fetching data via the gRPC interface. gRPC panels are nonzero mainly when reporting/Clio-style clients are connected. + +**Scope:** per node — measured on and specific to this individual server. + + + +### gRPC + +gRPC is a high-performance binary RPC protocol the node exposes for specific consumers, chiefly reporting-mode (Clio) clients. gRPC traffic is zero on nodes without such clients; its status is only ever success or error. + +**Scope:** per node — measured on and specific to this individual server. + + + +### Order book + +An order book holds the standing offers to exchange a given currency pair in the XRP Ledger's decentralized exchange. Pathfinding and some RPC queries walk order books; cost grows with order-book depth and request complexity. + +**Scope:** network-wide — a protocol-shared value, the same across all nodes. + +**See also:** [Order book on xrpl.org](https://xrpl.org/docs/concepts/tokens/decentralized-exchange) + + + +### Path request / discovery + +A path request is a client subscription for payment paths; discovery passes are the periodic refreshes the node runs to keep those paths current as ledgers close. Discovery cost tracks request demand and is a cost driver for subscription-heavy nodes. + +**Scope:** per node — measured on and specific to this individual server. + +**See also:** [Path request / discovery on xrpl.org](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods/path-and-order-book-methods/path_find) + + + +### Pathfinding (fast / full) + +Pathfinding searches for routes along which a cross-currency payment can flow through order books and AMMs. A fast search trades accuracy for speed; a full search is exhaustive and much more expensive. Sustained high durations indicate pathfinding-heavy clients straining the node. + +**Scope:** per node — measured on and specific to this individual server. + +**See also:** [Pathfinding (fast / full) on xrpl.org](https://xrpl.org/docs/concepts/tokens/fungible-tokens/paths) + + + +### Resource drops / warnings + +The resource manager meters each peer and client; it first warns endpoints for excessive usage and then drops or blocks them. Nonzero rates mean the node is actively rejecting abusive connections; zero is expected when no abusive consumers are present. + +**Scope:** per node — measured on and specific to this individual server. + + + +### RPC command / method + +Clients call the node via named RPC commands (also called methods), such as account_info, ledger, or submit, over HTTP, WebSocket, or gRPC. Panels break rate, latency, and errors down by command; heavy commands (ledger/account queries) cost far more than status calls. + +**Scope:** per node — measured on and specific to this individual server. + +**See also:** [RPC command / method on xrpl.org](https://xrpl.org/docs/references/http-websocket-apis/public-api-methods) + + + +### WebSocket + +WebSocket is a long-lived connection transport for the node's API, used by clients that subscribe to streams or send many requests. WebSocket message rate is nonzero only when clients use WebSocket; HTTP-only nodes read zero. + +**Scope:** per node — measured on and specific to this individual server. + +**See also:** [WebSocket on xrpl.org](https://xrpl.org/docs/references/http-websocket-apis/api-conventions) diff --git a/docs/telemetry-runbook.md b/docs/telemetry-runbook.md index 8582a8dd6a..2a61fb51f7 100644 --- a/docs/telemetry-runbook.md +++ b/docs/telemetry-runbook.md @@ -1,12 +1,55 @@ # xrpld Telemetry Operator Runbook +## Table of Contents + +- [Overview](#overview) +- [Quick Start](#quick-start) +- [Configuration Reference](#configuration-reference) +- [Exporting to Grafana Cloud](#exporting-to-grafana-cloud) +- [Span Reference](#span-reference) + - [RPC Spans](#rpc-spans) + - [Transaction Spans](#transaction-spans) + - [Transaction Queue Spans](#transaction-queue-spans) + - [PathFinding Spans](#pathfinding-spans) + - [Consensus Spans](#consensus-spans) + - [Ledger Spans](#ledger-spans) + - [Peer Spans](#peer-spans) +- [Protocol Span Flow](#protocol-span-flow) + - [Master overview](#master-overview) + - [Client and peer ingress](#client-and-peer-ingress) + - [Shared transaction apply pipeline](#shared-transaction-apply-pipeline) + - [Consensus round](#consensus-round) + - [Accept, build, and finalize the ledger](#accept-build-and-finalize-the-ledger) + - [Side flows: pathfinding and ledger acquire](#side-flows-pathfinding-and-ledger-acquire) + - [Where telemetry parenting differs from protocol flow](#where-telemetry-parenting-differs-from-protocol-flow) +- [Insights and Sample Queries](#insights-and-sample-queries) + - [Transaction Workflow Analysis](#transaction-workflow-analysis) + - [DEX (OfferCreate / OfferCancel)](#dex-offercreate--offercancel) + - [Apply Pipeline by Stage](#apply-pipeline-by-stage) + - [Transaction Queue Health](#transaction-queue-health) + - [RPC Debugging](#rpc-debugging) + - [PathFinding Performance](#pathfinding-performance) + - [Consensus Health](#consensus-health) + - [Cross-Subsystem Correlation](#cross-subsystem-correlation) +- [Cross-Node Trace Propagation](#cross-node-trace-propagation) +- [Prometheus Metrics (Spanmetrics)](#prometheus-metrics-spanmetrics) +- [System Metrics (OTel native -- beast::insight)](#system-metrics-otel-native----beastinsight) +- [Grafana Dashboards](#grafana-dashboards) +- [Alerting](#alerting) +- [Log-Trace Correlation](#log-trace-correlation) +- [Troubleshooting](#troubleshooting) +- [Performance Tuning](#performance-tuning) +- [Disabling Telemetry](#disabling-telemetry) + ## Overview xrpld supports OpenTelemetry distributed tracing to provide visibility into RPC requests, transaction processing, and consensus rounds. This runbook covers operating a running node and querying its traces. For building xrpld with telemetry support and the internal architecture, see -[build/telemetry.md](build/telemetry.md). +[build/telemetry.md](build/telemetry.md). For plain-language definitions of the +XRP Ledger terms used in the dashboards, see the +[telemetry glossary](telemetry-glossary.md). ## Quick Start @@ -42,6 +85,39 @@ cmake --preset default -Dtelemetry=ON cmake --build --preset default ``` +### 4. Run against a live network + +Two ready-made configs connect a tracking node (no validator credentials) to a +public network with all tracing and native metrics enabled: + +| Config | Network | +| ---------------------------------------------- | ------- | +| `docker/telemetry/xrpld-telemetry.cfg` | Devnet | +| `docker/telemetry/xrpld-telemetry-mainnet.cfg` | Mainnet | + +```bash +.build/xrpld --conf docker/telemetry/xrpld-telemetry-mainnet.cfg +``` + +Both set `[insight] server=otel` (native metrics → collector → Prometheus, which +drives the dashboards) and `service_instance_id`, exposed by Prometheus as the +`service_instance_id` label that the `$node` dashboard variable filters on. The +mainnet config logs to `/var/log/xrpld/mainnet/debug.log` — the path +the collector's filelog receiver tails for log-trace correlation. + +Metrics begin flowing as soon as the node connects to peers (`server_state` +≥ `connected`); full ledger and consensus panels populate after sync +(`server_state` = `full`). Check progress with: + +```bash +curl -s http://localhost:5005 -d '{"method":"server_info"}' | + jq '.result.info | {server_state, peers, complete_ledgers}' +``` + +> Mainnet sync is bandwidth- and disk-heavy. For a quick check use the devnet +> config or the standalone test in `docker/telemetry/TESTING.md`, which +> generates spans without waiting for a live sync. + ## Configuration Reference | Option | Default | Description | @@ -64,6 +140,96 @@ cmake --build --preset default | `tls_client_cert` | (empty) | Client cert (PEM) for mutual TLS; empty = one-way TLS | | `tls_client_key` | (empty) | Private key (PEM) for `tls_client_cert` | +## Exporting to Grafana Cloud + +The collector can ship traces, metrics, and logs to a hosted **Grafana +Cloud** stack instead of (or alongside) the local Tempo/Prometheus/Loki +backends. This is a runtime choice — no xrpld rebuild and no change to the +base stack. xrpld still exports to the local collector exactly as before; +the collector adds one OTLP/HTTP exporter that forwards all three signals to +the Grafana Cloud OTLP gateway, which fans them out to hosted Tempo, Mimir, +and Loki. + +### Credentials + +Find these under **Grafana Cloud → Connections → OpenTelemetry (OTLP)**: + +| Value | Used as | Notes | +| ----------------------------- | ----------------- | -------------------------------------------------- | +| `GRAFANA_CLOUD_OTLP_ENDPOINT` | exporter endpoint | Full gateway URL incl. `/otlp` path | +| `GRAFANA_CLOUD_INSTANCE_ID` | Basic-auth user | Numeric stack/instance id | +| `GRAFANA_CLOUD_API_TOKEN` | Basic-auth pass | Access-policy token with `*:write` for all signals | + +### Enable + +1. Copy the template and fill in the three values: + + ```bash + cp docker/telemetry/.env.grafanacloud.example docker/telemetry/.env.grafanacloud + # edit .env.grafanacloud — this file is gitignored, never commit tokens + ``` + +2. Bring the stack up with the base file **and** the Grafana Cloud override: + + ```bash + docker compose -f docker/telemetry/docker-compose.yml \ + -f docker/telemetry/docker-compose.grafanacloud.yaml up -d + ``` + +To return to local-only export, bring the stack up with just the base +`docker-compose.yml`. + +### Files + +| File | Role | +| ----------------------------------------- | ---------------------------------------------------------------------------------------------- | +| `otel-collector-config.grafanacloud.yaml` | Collector config: local backends **plus** a Grafana Cloud OTLP exporter on all three pipelines | +| `docker-compose.grafanacloud.yaml` | Override that mounts that config and injects the credentials | +| `.env.grafanacloud.example` | Credential template (copy to `.env.grafanacloud`) | + +### Local + cloud vs cloud-only + +The prepared config **dual-exports**: data goes to both the local stack and +Grafana Cloud, so the on-box backends remain a fallback. For cloud-only, +remove the local exporters (`debug`, `otlp/tempo`, `prometheus`, +`otlphttp/loki`) from the respective pipelines in +`otel-collector-config.grafanacloud.yaml`, leaving only +`otlphttp/grafanacloud`. + +> **Note**: shipping logs to Grafana Cloud requires keeping xrpld file +> logging on (at least `warning` level) so the collector's filelog receiver +> has a `debug.log` to tail. Traces and metrics are unaffected by log level. + +### Importing dashboards to Grafana Cloud + +Shipping data (above) is independent of installing the dashboards. The local +stack auto-provisions dashboards from a mounted folder +(`grafana/provisioning/dashboards/dashboards.yaml`, `type: file`); Grafana +Cloud cannot read your filesystem, so its dashboards must be imported over the +HTTP API or the UI. + +The dashboard JSON in `docker/telemetry/grafana/dashboards/` references its +backends through datasource **template variables** (`${DS_PROMETHEUS}`, +`${DS_TEMPO}`) rather than fixed UIDs. On import, Grafana binds each variable +to a datasource of the matching type — auto-selecting it when only one exists +(the usual case: one Mimir, one Tempo). This is what makes the same files work +unchanged on both the local stack and Cloud. + +> Dashboards are parameterized by `grafana/parameterize-datasources.py`. If you +> add a dashboard exported with hardcoded UIDs, re-run that script (idempotent) +> before committing so it stays portable. + +To import: + +1. In Grafana Cloud, go to **Dashboards → New → Import**. +2. Upload a file from `docker/telemetry/grafana/dashboards/` (or paste its + JSON), then click **Load**. +3. At the datasource prompt, confirm the auto-selected **Prometheus/Mimir** + datasource — and **Tempo** for dashboards that query traces — then + **Import**. +4. Repeat per dashboard. Only `consensus-health` uses Tempo; the rest need + only the Prometheus/Mimir datasource. + ## Span Reference All spans instrumented in xrpld, grouped by subsystem: @@ -194,6 +360,645 @@ child span that links back to the sending node is the separate --- +## Protocol Span Flow + +This section maps every span type onto the **real xrpld control flow and XRPL +protocol order** (verified against code and [docs/consensus.md](consensus.md)) — +what the code actually executes next, in what order, with which loops and +branches. Spans are drawn as **labels on real operations**, not as their +OpenTelemetry parent links; the SDK's span parenting is listed separately in +[Where telemetry parenting differs from protocol flow](#where-telemetry-parenting-differs-from-protocol-flow). + +These diagrams are the **canonical key for linking the span hierarchy** — every +node and every branch is labelled with the span that represents that state or +transition, so a span can be wired to its true protocol parent/child by reading +the graph. They are therefore drawn **exact, not simplified**: every real loop, +retry, recovery, and drop branch is shown even when it adds clutter. + +Naming and edge conventions: + +- **Rectangle `[ ]`** — a state/operation that **emits a span**; the first line is + the exact `span.name`, the parenthetical below is the operation. +- **Rounded `( )` with `(no span)`** — a real protocol step that emits **no span**; + shown so the flow stays continuous and is never mistaken for a missing span. +- **Solid arrow** — the code calls or sequences directly into the next operation. + When the transition itself emits a span, the edge is labelled `→[span.name]`; + otherwise it carries the branch condition. +- **`↻`** — the edge repeats (per tx, per peer, per dispute, per pass, per round). +- **Dashed arrow** — a conditional branch or an async job hand-off. +- **Dotted `⇢ ctx`** — trace context crosses a node boundary over a protobuf peer + message (`sender.span ⇢ receiver.span`); a different node continues the trace — + **not** an in-process call. +- **Red-bordered node** — a terminal **drop / abandon** state. + +### Master overview + +Five ingress origins feed two shared engines — the per-transaction **apply +pipeline** and the **consensus round** — which converge on ledger build → store → +validate. Pathfinding and ledger-acquire are side flows. A single ledger can take +**many consensus rounds** to settle (see [Consensus round](#consensus-round)). + +```mermaid +flowchart TB + classDef ingress fill:#1d4ed8,stroke:#1e3a8a,color:#fff; + classDef engine fill:#047857,stroke:#064e3b,color:#fff; + classDef consensus fill:#b45309,stroke:#7c2d12,color:#fff; + classDef ledger fill:#6d28d9,stroke:#4c1d95,color:#fff; + classDef side fill:#0e7490,stroke:#155e75,color:#fff; + classDef plain fill:#334155,stroke:#0f172a,color:#fff; + + subgraph ING["Ingress (protocol entry points)"] + direction TB + RPC["rpc.http_request / rpc.ws_message
rpc.ws_upgrade / grpc.MethodName
(client transport in)"]:::ingress + SUB(["submit command
(no span)"]):::plain + PRELAY["tx.receive
(peer relay in)"]:::ingress + PMSG["peer.proposal.receive
peer.validation.receive
(peer overlay in)"]:::ingress + end + + TXP["tx.process
(NetworkOPs::processTransaction)"]:::engine + OPEN["txq.enqueue
(open-ledger apply + TxQ decision)"]:::engine + PIPE["tx.preflight → tx.preclaim → tx.transactor
(SHARED apply pipeline)"]:::engine + + subgraph CONS["Consensus round"] + direction TB + ROUND["consensus.round
(Open → Establish → Accepted)"]:::consensus + ACC["consensus.accept → consensus.accept.apply"]:::consensus + end + + subgraph LGR["Ledger finalize"] + direction TB + BUILD["ledger.build
(tx.apply over agreed set)"]:::ledger + STORE["ledger.store
(built, NOT yet final)"]:::ledger + VAL["ledger.validate
(promoted at quorum)"]:::ledger + end + + subgraph SIDE["Side flows"] + direction TB + PF["pathfind.update_all
pathfind.request/compute/discover"]:::side + ACQ["ledger.acquire
(fetch missing / correct prior)"]:::side + end + + RPC -.->|submit / submit_multisigned| SUB --> TXP + RPC -.->|path_find / ripple_path_find| PF + PRELAY --> TXP + TXP --> OPEN + OPEN -->|↻ up to 3 passes| PIPE + OPEN -.->|txq.accept re-apply queued tx each close ↻| PIPE + PMSG -->|peerProposal / recvValidation| ROUND + ROUND --> ACC --> BUILD + BUILD -->|↻ each tx × up to 3 passes| PIPE + BUILD --> STORE + PMSG -. "trusted validations arrive async → checkAccept quorum" .-> VAL + ROUND -. "avalanche rounds ↻ (threshold 50→65→70→95%)" .-> ROUND + ACC -->|endConsensus ↻ next round until a ledger validates| ROUND + ROUND -.->|wrong-ledger: request correct prior| ACQ + ACQ -.->|switch-ledger: resume round on correct prior| ROUND + ACQ --> STORE + VAL -.->|missing ledger| ACQ + BUILD -.->|every close re-runs| PF +``` + +### Client and peer ingress + +RPC submit and peer relay **converge** at `tx.process`, the single NetworkOPs +entry. gRPC serves ledger queries only — it has no submit path and never runs +`doCommand`. + +```mermaid +flowchart TB + classDef span fill:#1d4ed8,stroke:#1e3a8a,color:#fff; + classDef plain fill:#334155,stroke:#0f172a,color:#fff; + classDef drop fill:#7f1d1d,stroke:#ef4444,color:#fff; + + HTTP["rpc.http_request
(HTTP entry)"]:::span + PROC["rpc.process
(parse + batch)"]:::span + CMD["rpc.command.NAME
(one command)"]:::span + WSU["rpc.ws_upgrade
(WS handshake)"]:::span + WSM["rpc.ws_message
(one frame)"]:::span + GRPC["grpc.MethodName
(ledger query)"]:::span + GH(["handler_ ctx
(no span)"]):::plain + SUBMIT(["doSubmit
(no span)"]):::plain + TXP["tx.process
(NetworkOPs::processTransaction)"]:::span + RELAYOUT(["Overlay::relay fan-out to N peers
(no span; if applied / terQUEUED,
shouldRelay, not tfInnerBatchTxn)"]):::plain + PREDROP(["Diverged / needNetworkLedger
(no span — dropped before tx.receive)"]):::drop + RCV["tx.receive
(peer TMTransaction in)"]:::span + RCVDROP["tx.receive
tx_status = rejected_inner_batch /
suppressed / dropped_no_sync /
dropped_queue_full"]:::drop + CHK(["checkTransaction
(JtTransaction worker, no span)"]):::plain + PRELAY_IN(["TMTransaction in (no span)"]):::plain + + HTTP -->|processRequest| PROC + PROC -->|↻ each batch request → doCommand| CMD + WSU -. "each inbound frame → onWSMessage" .-> WSM + WSM -->|doCommand| CMD + GRPC --> GH + CMD -.->|submit / submit_multisigned| SUBMIT + SUBMIT -->|processTransaction| TXP + TXP -.->|relay applied / queued tx| RELAYOUT + + PRELAY_IN -.->|tracking == Diverged / needNetworkLedger| PREDROP + PRELAY_IN -->|else| RCV + RCV -.->|inner-batch / dup / age>4min / JtTransaction full| RCVDROP + RCV -->|addJob JtTransaction| CHK + CHK -->|processTransaction, trusted=peer| TXP + + RELAYOUT -. "tx.process ⇢ tx.receive (span_id over TMTransaction)" .-> RCV +``` + +Ingress branches (all evidence in code): + +- `onHandoff`: WS upgrade vs peer bundle vs status page vs legacy HTTP + ([ServerHandler.cpp:227](../src/xrpld/rpc/detail/ServerHandler.cpp#L227)). +- `doSubmit`: `tx_blob` present → submit signed blob; absent → server + sign-and-submit ([Submit.cpp:49](../src/xrpld/rpc/handlers/transaction/Submit.cpp#L49)). +- `tx.process`: local RPC → `doTransactionSync`; peer → `doTransactionAsync` + (JtBatch) ([NetworkOPs.cpp:1434](../src/xrpld/app/misc/NetworkOPs.cpp#L1434)). +- **Pre-span peer drops** (no `tx.receive` created): `Diverged` + ([PeerImp.cpp:1299](../src/xrpld/overlay/detail/PeerImp.cpp#L1299)) / + `needNetworkLedger` ([1302](../src/xrpld/overlay/detail/PeerImp.cpp#L1302)), + before the span at ~1320. +- **Post-span peer drops** (span exists, `tx_status` set, no job enqueued): + `tfInnerBatchTxn` ([1348](../src/xrpld/overlay/detail/PeerImp.cpp#L1348)), + HashRouter dup/`BAD` ([1361](../src/xrpld/overlay/detail/PeerImp.cpp#L1361)), + `dropped_no_sync` when validated-ledger age > 4 min + ([1416](../src/xrpld/overlay/detail/PeerImp.cpp#L1416)), `dropped_queue_full` + when `JtTransaction` jobs > `maxTransactions` + ([1421](../src/xrpld/overlay/detail/PeerImp.cpp#L1421)). +- **Relay fan-out**: an accepted/queued `tx.process` relays to N peers via + `Overlay::relay`, gated on `applied || (non-FULL local) || terQUEUED`, + HashRouter `shouldRelay`, and not `tfInnerBatchTxn`; the span context is + injected here ([NetworkOPs.cpp:1797](../src/xrpld/app/misc/NetworkOPs.cpp#L1797)). + +Inbound consensus messages take a two-stage handler — a fresh-root `peer.*.receive` +span created first (kConsumer, always), then a `consensus.*.receive` span (only if +not dropped) that carries the sender's context — before enqueuing a `checkPropose` +/ `checkValidation` worker job. The drop points are **asymmetric**: proposals drop +entirely before `consensus.proposal.receive`, while validations can drop both +before and after `consensus.validation.receive`. + +```mermaid +flowchart TB + classDef span fill:#0e7490,stroke:#155e75,color:#fff; + classDef plain fill:#334155,stroke:#0f172a,color:#fff; + classDef drop fill:#7f1d1d,stroke:#ef4444,color:#fff; + + PPR["peer.proposal.receive
(freshRoot, always)"]:::span + PPRDROP(["no consensus.proposal.receive
(untrusted+relay-off / dup /
untrusted+Diverged / untrusted+loaded)"]):::drop + CPR["consensus.proposal.receive
(carries sender ctx)"]:::span + CP(["checkPropose worker
(no span)"]):::plain + SIGP(["sig-fail: charge, drop
(no relay, no span)"]):::drop + PTP(["processTrustedProposal → peerProposal
(no span)"]):::plain + + PVR["peer.validation.receive
(freshRoot, always)"]:::span + pvrDrop1(["no consensus.validation.receive
(!isCurrent / relay-off / dup)"]):::drop + CVR["consensus.validation.receive
(carries sender ctx)"]:::span + cvrDrop2(["dropped after span
(untrusted+Diverged /
untrusted+loaded → no job/relay)"]):::drop + CV(["checkValidation worker
(no span)"]):::plain + SIGV(["!isValid: charge, drop
(no span)"]):::drop + RV(["recvValidation → handleNewValidation
(no span)"]):::plain + RELAY(["Overlay::relay fan-out to N peers
(no span)"]):::plain + + PPR -.->|4 drop conditions| PPRDROP + PPR -->|else| CPR + CPR -->|addJob JtProposalT/Ut| CP + CP -.->|!checkSign| SIGP + CP -->|isTrusted| PTP + CP -.->|if relay| RELAY + + PVR -.->|3 drop conditions| pvrDrop1 + PVR -->|else| CVR + CVR -.->|untrusted+Diverged / loaded| cvrDrop2 + CVR -->|addJob JtValidationT/Ut| CV + CV -.->|!isValid| SIGV + CV -->|recvValidation| RV + CV -.->|if relay / cluster| RELAY +``` + +Consensus-message drop evidence: + +- Both `peer.proposal.receive` and `peer.validation.receive` are `freshRoot` + spans created at the top of `onMessage` + ([PeerImp.cpp:1766](../src/xrpld/overlay/detail/PeerImp.cpp#L1766), + [2389](../src/xrpld/overlay/detail/PeerImp.cpp#L2389)) — so they exist even for + dropped messages. +- **Proposal drops (all before `consensus.proposal.receive` at + [1868](../src/xrpld/overlay/detail/PeerImp.cpp#L1868))**: untrusted+relay-off + ([1807](../src/xrpld/overlay/detail/PeerImp.cpp#L1807)), duplicate + ([1832](../src/xrpld/overlay/detail/PeerImp.cpp#L1832)), untrusted+Diverged + ([1840](../src/xrpld/overlay/detail/PeerImp.cpp#L1840)), untrusted+loaded + ([1846](../src/xrpld/overlay/detail/PeerImp.cpp#L1846)). +- **Validation drops (asymmetric around `consensus.validation.receive` at + [2476](../src/xrpld/overlay/detail/PeerImp.cpp#L2476))**: before — `!isCurrent` + ([2426](../src/xrpld/overlay/detail/PeerImp.cpp#L2426)), relay-off + ([2445](../src/xrpld/overlay/detail/PeerImp.cpp#L2445)), duplicate + ([2468](../src/xrpld/overlay/detail/PeerImp.cpp#L2468)); after — untrusted+Diverged + ([2489](../src/xrpld/overlay/detail/PeerImp.cpp#L2489)), untrusted+loaded + ([2506](../src/xrpld/overlay/detail/PeerImp.cpp#L2506)). +- **Worker sig-fail drops** (charged `kFeeInvalidSignature`, suppress processing + and relay): `checkPropose !checkSign` + ([PeerImp.cpp:3105](../src/xrpld/overlay/detail/PeerImp.cpp#L3105)), + `checkValidation !isValid` + ([3149](../src/xrpld/overlay/detail/PeerImp.cpp#L3149)). + +### Shared transaction apply pipeline + +The apply pipeline is the **single protocol tx-processing chain**, expressed in +code as one composed call +([apply.cpp:118](../src/libxrpl/tx/apply.cpp#L118)): +`doApply(preclaim(preflight(), …), …)`. C++ evaluates inner-to-outer, so +`preflight` runs first, feeds `preclaim`, which feeds `doApply`. Each stage +inspects the prior stage's `TER` and no-ops if it already failed. + +**Four invokers** point into this one pipeline; the diagram draws it once. + +```mermaid +flowchart TB + classDef span fill:#047857,stroke:#064e3b,color:#fff; + classDef plain fill:#334155,stroke:#0f172a,color:#fff; + classDef inv fill:#1d4ed8,stroke:#1e3a8a,color:#fff; + classDef drop fill:#7f1d1d,stroke:#ef4444,color:#fff; + + I1(["open-ledger applyOne (no span)
↻ up to 3 passes"]):::inv + I2["txq.apply_direct
(fee ≥ required)"]:::span + I3["txq.batch_clear / txq.accept_tx
↻ per queued tx"]:::span + I4["tx.apply
(consensus set, ↻ each tx × 3 passes)"]:::span + + REPF(["txq path: rules/flags changed?
re-run preflight (no span)"]):::plain + FREE(["xrpl::apply() (no span)"]):::plain + PF["tx.preflight
(stateless checks)"]:::span + PC["tx.preclaim
(ledger-aware checks)"]:::span + TR["tx.transactor
(mutate stage)"]:::span + CLS(["classify final TER (no span)"]):::plain + OK(["Success / erase (no span)"]):::plain + FAIL(["tef / tem / tel → hard fail, erase"]):::drop + RETRY(["retriable ter → keep in set (no span)"]):::plain + + I1 --> FREE + I2 --> FREE + I3 --> REPF --> FREE + I4 --> FREE + FREE --> PF + PF -->|preflight tesSUCCESS| PC + PF -. "else → classify (no preclaim/transactor)" .-> CLS + PC -->|likelyToClaimFee| TR + PC -. "else → classify (no transactor)" .-> CLS + TR --> CLS + CLS --> OK + CLS --> FAIL + CLS --> RETRY + RETRY -. "next pass while pass<3 and changes>0" .-> FREE + RETRY -. "last pass → drop from set" .-> FAIL +``` + +Pipeline gates and retry (evidence): + +- `preclaim` short-circuits if preflight `!tesSUCCESS` + ([applySteps.cpp:498](../src/libxrpl/tx/applySteps.cpp#L498)); `doApply` + short-circuits if `!likelyToClaimFee` + ([applySteps.cpp:532](../src/libxrpl/tx/applySteps.cpp#L532)); the transactor + mutates only when preclaim is `tesSUCCESS` + ([Transactor.cpp:1647](../src/libxrpl/tx/Transactor.cpp#L1647)). +- **Final-TER classification**: `applied` → Success; `tef | tem | tel` → hard Fail; + else → Retry ([apply.cpp:226](../src/libxrpl/tx/apply.cpp#L226)). +- **Multi-pass retry**: both open-ledger `applyOne` and consensus `tx.apply` loop + `pass < LEDGER_TOTAL_PASSES` (= 3); a `Retry` tx is kept for the next pass, and + the final pass converts lingering retriable txs into drops + ([OpenLedger.h:237](../src/xrpld/app/ledger/OpenLedger.h#L237), + [BuildLedger.cpp:129](../src/xrpld/app/ledger/detail/BuildLedger.cpp#L129); + `LEDGER_TOTAL_PASSES` [OpenLedger.h:29](../src/xrpld/app/ledger/OpenLedger.h#L29)). +- **TxQ re-preflight**: the queue path re-runs `preflight` when the ledger's + rules/flags changed since enqueue + ([TxQ.cpp:315](../src/xrpld/app/misc/detail/TxQ.cpp#L315)). +- **TxQ cross-ledger retry**: a queued tx that fails with a retriable result keeps its slot with + `--retriesRemaining` (`kRetriesAllowed` = 10) and is re-applied at a **later** + ledger close; on `retriesRemaining ≤ 0` or `tef|tem` it is dropped with an + account `retryPenalty` ([TxQ.cpp:1528](../src/xrpld/app/misc/detail/TxQ.cpp#L1528)). +- `TxQ::apply` outcome fork: preflight-reject / `applied_direct` / `batch_clear` / + `queued` (`terQUEUED`) / reject + ([TxQ.cpp:762](../src/xrpld/app/misc/detail/TxQ.cpp#L762)). + +> **`tx.apply` is set-level, consensus-only.** It wraps the retry-pass loop over +> the agreed set during `buildLedger` and exists on **no other** invoker. It is +> not a per-transaction span, and TxQ / open-ledger apply create no `tx.apply`. + +### Consensus round + +`beginConsensus → startRound` starts the round (`consensus.round`, `Open` phase). +The **heartbeat timer** drives `Consensus::timerEntry` each pass; the round stays +in `Establish` across many heartbeats until the outcome is decided. + +> **A single ledger can take many rounds to settle.** Two nested multi-round +> mechanisms (see [docs/consensus.md](consensus.md)): +> +> 1. **Avalanche rounds inside one Establish phase** — each `timerEntry` runs +> `phaseEstablish` again (`establishCounter_++`) and raises the inclusion +> threshold **50% → 65% → 70% → 95%** as the round ages +> ([ConsensusParms.h:145](../src/xrpld/consensus/ConsensusParms.h#L145)). +> `checkConsensus` returning `No` keeps the node in `Establish` and loops; a +> round cannot even `Expire` before a minimum of +> `avalancheCutoffs.size() × avMinRounds = 4 × 2 = 8` passes +> ([Consensus.h:1938](../src/xrpld/consensus/Consensus.h#L1938)). +> 2. **Retry across consensus rounds** — a round can end `MovedOn` / `Expired`, +> meaning the network settled a _different_ ledger. The node still builds a +> ledger, but the **next** round's `checkLedger` detects the wrong prior, +> switches to `WrongLedger` / `SwitchedLedger` mode, acquires the correct +> ledger, and re-deliberates. A ledger is only truly settled once trusted +> **validations reach quorum** (`ledger.validate`); the alternate is abandoned. + +```mermaid +flowchart TB + classDef span fill:#b45309,stroke:#7c2d12,color:#fff; + classDef plain fill:#334155,stroke:#0f172a,color:#fff; + + BEGIN(["beginConsensus → startRound
(Proposing OR Observing; no span)"]):::plain + ROUND["consensus.round
(one attempt at next ledger)"]:::span + OPENS["consensus.phase.open
(collect txs; buffer peer
proposals / gotTxSet)"]:::span + HB(["heartbeat → timerEntry
(every LEDGER_MIN_CLOSE; no span)"]):::plain + CKL(["checkLedger
(correct prior ledger? no span)"]):::plain + WRONG(["handleWrongLedger → leaveConsensus (no span):
if Proposing send BOW-OUT,
mode → Observing; acquire ledger"]):::plain + MODE["consensus.mode_change
(mode transition)"]:::span + POPEN(["phaseOpen: shouldCloseLedger? (no span)"]):::plain + CLOSE["consensus.ledger_close
(close open ledger, seed disputes)"]:::span + pSend["consensus.proposal.send
(broadcast our position)"]:::span + PEST["consensus.establish
(phaseEstablish; avalanche round ↻
threshold 50→65→70→95%)"]:::span + UPOS["consensus.update_positions
(add/drop disputed txs; child of establish)"]:::span + acqTx(["acquireTxSet → gotTxSet
(async peer tx set; no span)"]):::plain + PAUSE(["shouldPause?
(wait on laggards; no span)"]):::plain + CHECK["consensus.check
(checkConsensus; child of establish)"]:::span + CTC(["haveCloseTimeConsensus?
(else agree-to-disagree +1s; no span)"]):::plain + ACCEPT["consensus.accept
(round complete)"]:::span + + BEGIN --> ROUND --> OPENS + HB -->|under mutex| CKL + CKL -.->|wrong prior| WRONG + WRONG --> MODE + WRONG -. "recovered → re-enter Open (playbackProposals)" .-> OPENS + WRONG -. "still missing → keep deliberating, defer to peers" .-> HB + CKL -->|prior OK| POPEN + HB -.->|phase==Open| POPEN + HB -.->|phase==Establish| PEST + POPEN -.->|shouldClose| CLOSE + CLOSE -.->|mode==Proposing| pSend + PEST --> UPOS + UPOS -.->|position changed && Proposing| pSend + UPOS -.->|disagreeing peer position| acqTx + acqTx -. "gotTxSet ↻ → new disputes" .-> UPOS + PEST --> PAUSE + PAUSE -. "pausing → wait (loop)" .-> HB + PAUSE -->|ready| CHECK + CHECK -. "No / Expired < 8 passes → next avalanche round" .-> HB + CHECK --> CTC + CTC -. "no CT consensus → loop" .-> HB + CTC -.->|Yes / MovedOn / Expired ≥ 8| ACCEPT + ROUND -.->|mode set at start| MODE + ACCEPT -. "endConsensus → next round ↻ (until a ledger validates)" .-> BEGIN +``` + +Consensus loops and branches (evidence): + +- **`consensus.establish` is the parent of `update_positions` and `check`**: + `phaseEstablish` creates the establish span (`startEstablishTracing`), and both + child spans parent to its captured context + ([Consensus.h:2100](../src/xrpld/consensus/Consensus.h#L2100), + [1629](../src/xrpld/consensus/Consensus.h#L1629), + [1838](../src/xrpld/consensus/Consensus.h#L1838)). +- **Avalanche-convergence loop (rounds within one ledger)**: repeated + `heartbeat → timerEntry → phaseEstablish` bumps `establishCounter_` and raises + the inclusion threshold each pass; `checkConsensus` = `No` stays in `Establish` + ([NetworkOPs.cpp:1214](../src/xrpld/app/misc/NetworkOPs.cpp#L1214); + [Consensus.h:1468](../src/xrpld/consensus/Consensus.h#L1468); + thresholds [ConsensusParms.h:145](../src/xrpld/consensus/ConsensusParms.h#L145)). +- **Retry-across-rounds loop (many rounds per settled ledger)**: `MovedOn` / + `Expired` accepts a non-preferred ledger; the next round's `checkLedger` finds + the wrong prior and recovers before re-deliberating + ([Consensus.h:1194](../src/xrpld/consensus/Consensus.h#L1194)); round-to-round + via `endConsensus → beginConsensus` + ([NetworkOPs.cpp:2315](../src/xrpld/app/misc/NetworkOPs.cpp#L2315)). +- **Two extra establish loop-backs before accept**: `shouldPause` (laggard + backpressure) and `!haveCloseTimeConsensus_` (TX consensus but not close-time) + each `return` and re-loop, distinct from `checkConsensus == No` + ([Consensus.h:1497](../src/xrpld/consensus/Consensus.h#L1497), + [1500](../src/xrpld/consensus/Consensus.h#L1500)); close time can + "agree to disagree" at prior close + 1s ([docs/consensus.md:163](consensus.md)). +- **acquireTxSet / gotTxSet loop**: a disagreeing peer position triggers an async + `acquireTxSet`; the later `gotTxSet` regenerates disputes and can extend the + establish phase ([Consensus.h:932](../src/xrpld/consensus/Consensus.h#L932)). +- **Bow-out / mode change**: `handleWrongLedger → leaveConsensus` sends a bow-out + proposal and demotes Proposing → Observing for the rest of the round + ([Consensus.h:1977](../src/xrpld/consensus/Consensus.h#L1977)); `startRound` + begins in Proposing **or** Observing ([docs/consensus.md:176](consensus.md)). +- **Buffered Open-phase inputs**: `peerProposal` / `gotTxSet` arriving during Open + are stored, then seeded as disputes at `closeLedger` (`createDisputes`); + `playbackProposals` replays them at `startRound` / `handleWrongLedger` + ([docs/consensus.md:244](consensus.md); + [Consensus.h:817](../src/xrpld/consensus/Consensus.h#L817)). +- **Outcome fork** after `checkConsensus`: `No` (loop) / `Yes` (onAccept) / + `MovedOn` / `Expired` ([Consensus.h:1516](../src/xrpld/consensus/Consensus.h#L1516)). +- **Expired guard**: a round cannot leave on `Expired` before + `avalancheCutoffs.size() × avMinRounds` (= 8) passes — below that, `Expired` + loops like `No` ([Consensus.h:1938](../src/xrpld/consensus/Consensus.h#L1938)). +- The **deterministic-vs-random trace-strategy** branch at round start + ([RCLConsensus.cpp:1291](../src/xrpld/app/consensus/RCLConsensus.cpp#L1291)) sets + only the trace ID — it has **zero protocol effect**. + +### Accept, build, and finalize the ledger + +`onAccept` enqueues a `JtAccept` job; `doAccept` runs on that worker +(`consensus.accept.apply`). It builds the ledger (running the apply pipeline over +the agreed set), cleans the queue, stores the ledger, optionally broadcasts a +validation, and rebuilds the open ledger. A built ledger is **not final** — it is +promoted to `ledger.validate` only when trusted validations reach quorum, an +**async, validation-driven** path re-entered per incoming trusted validation; a +built ledger that loses is **abandoned**. + +```mermaid +flowchart TB + classDef span fill:#6d28d9,stroke:#4c1d95,color:#fff; + classDef plain fill:#334155,stroke:#0f172a,color:#fff; + classDef drop fill:#7f1d1d,stroke:#ef4444,color:#fff; + + onAcc["consensus.accept
(round complete)"]:::span + APPLY["consensus.accept.apply
(JtAccept worker)"]:::span + BLCL(["buildLCL: replay data? (no span)"]):::plain + BUILD["ledger.build
(normal: apply agreed set)"]:::span + RPLY["ledger.build
(replay: TapNone, no tx.apply child)"]:::span + TXAP["tx.apply
(↻ each tx × up to 3 passes)"]:::span + CLEAN["txq.cleanup
(expire queue entries)"]:::span + STORE["ledger.store
(built, NOT yet final)"]:::span + vSend["consensus.validation.send
(broadcast our validation)"]:::span + CACC(["consensusBuilt → checkAccept
(quorum gate; no span)"]):::plain + NEWVAL(["inbound trusted validation
→ handleNewValidation → checkAccept
(async, per validation; no span)"]):::plain + VAL["ledger.validate
(promote highest-seq ledger ≥ quorum)"]:::span + LOSE(["built ledger loses:
never promoted → abandoned"]):::drop + OACC(["OpenLedger::accept
(rebuild open ledger; no span)"]):::plain + tqAcc["txq.accept
(↻ drain queued txs)"]:::span + swlStd(["switchLCL standalone:
setFullLedger + tryAdvance (no span)"]):::plain + swlNet(["switchLCL networked:
checkAccept (no span)"]):::plain + END(["endConsensus → next round ↻ (no span)"]):::plain + + onAcc -.->|addJob JtAccept| APPLY + APPLY --> BLCL + BLCL -->|normal path| BUILD --> TXAP + BLCL -. "replay path" .-> RPLY + APPLY --> CLEAN + APPLY --> STORE + APPLY -. "validating && isCompatible && !fail && canValidateSeq" .-> vSend + APPLY --> CACC + NEWVAL --> CACC + CACC -.->|highest-seq trusted ledger ≥ quorum| VAL + CACC -.->|tvc < quorum → no promotion| LOSE + APPLY --> OACC + OACC -->|TxQ::accept callback| tqAcc + OACC --> swlStd + OACC --> swlNet + swlStd -. "marks full-validated (no ledger.validate span)" .-> END + swlNet --> CACC + onAcc --> END +``` + +- Order inside `doAccept`: `buildLCL` (build → `tx.apply`, then `txq.cleanup`, + then `ledger.store`) → optional `validate` → `consensusBuilt`/`checkAccept` → + `OpenLedger::accept` (rebuilds the open ledger; `txq.accept` runs in its + callback) → `switchLCL` promotes the built ledger to the new LCL + ([RCLConsensus.cpp:812](../src/xrpld/app/consensus/RCLConsensus.cpp#L812) then + [833](../src/xrpld/app/consensus/RCLConsensus.cpp#L833)). +- **buildLCL replay branch**: if `releaseReplay()` has data, `buildLedger` replays + the stored set with `TapNone` — it **still emits `ledger.build`** (via + `buildLedgerImpl`) but applies txns directly with **no `tx.apply` child** and no + 3-pass loop; else the normal consensus-set path runs `tx.apply` over 3 passes + ([RCLConsensus.cpp:929](../src/xrpld/app/consensus/RCLConsensus.cpp#L929); + [BuildLedger.cpp:252](../src/xrpld/app/ledger/detail/BuildLedger.cpp#L252)). +- `processClosedLedger` (`txq.cleanup`) runs **after** build, **before** store + ([RCLConsensus.cpp:950](../src/xrpld/app/consensus/RCLConsensus.cpp#L950) vs + [953](../src/xrpld/app/consensus/RCLConsensus.cpp#L953)). +- **`ledger.validate` is async + lossy**: `checkAccept` is re-entered per incoming + trusted validation (`handleNewValidation → checkAccept`, + [RCLValidations.cpp:193](../src/xrpld/app/consensus/RCLValidations.cpp#L193)); + it promotes the **highest-seq** trusted ledger whose `valCount > neededValidations` + ([LedgerMaster.cpp:1180](../src/xrpld/app/ledger/detail/LedgerMaster.cpp#L1180)), + which may be a **different** ledger than the one this node built. Below quorum + (`tvc < minVal`) it returns early with no promotion — a built ledger that loses + is abandoned ([LedgerMaster.cpp:980](../src/xrpld/app/ledger/detail/LedgerMaster.cpp#L980); + [docs/consensus.md:50](consensus.md)). The `ledger.validate` span is emitted only + inside `checkAccept` ([LedgerMaster.cpp:987](../src/xrpld/app/ledger/detail/LedgerMaster.cpp#L987)). +- **validation-send guard**: broadcast only if + `validating_ && isCompatible && !consensusFail && canValidateSeq(seq)` — silently + suppressed for incompatible ledgers or an already-validated seq + ([RCLConsensus.cpp:730](../src/xrpld/app/consensus/RCLConsensus.cpp#L730)). +- **switchLCL**: standalone → `setFullLedger` + `tryAdvance` — marks the ledger + full-validated **without** emitting `ledger.validate` (that span lives only in + `checkAccept`); networked → `checkAccept` (shared async quorum gate) + ([LedgerMaster.cpp:442](../src/xrpld/app/ledger/detail/LedgerMaster.cpp#L442)). + +### Side flows: pathfinding and ledger acquire + +**Pathfinding** — an RPC one-shot (`path_find` / `ripple_path_find`) plus an async +recompute that fires on **every ledger close** for all active subscriptions, and +also garbage-collects dead subscriptions: + +```mermaid +flowchart TB + classDef span fill:#0e7490,stroke:#155e75,color:#fff; + classDef plain fill:#334155,stroke:#0f172a,color:#fff; + classDef drop fill:#7f1d1d,stroke:#ef4444,color:#fff; + + REQ["pathfind.request
(path_find / ripple_path_find)"]:::span + CREATE(["subscribe: makePathRequest →
persistent subscription (no span)"]):::plain + COMP["pathfind.compute
(doUpdate, one pass)"]:::span + DISC["pathfind.discover
(findPaths)"]:::span + PFDR(["Pathfinder + RippleCalc
↻ per source asset (no span)"]):::plain + UP(["updatePaths (JtUpdatePf, every close; no span)"]):::plain + UALL["pathfind.update_all
(recompute all active)"]:::span + DEAD(["dead subscriber → doAborting +
remove_if erase"]):::drop + + REQ -.->|subcommand create| CREATE + REQ -->|doUpdate| COMP -->|findPaths| DISC --> PFDR + UP -->|once per close| UALL + UALL -->|↻ each active request| COMP + UALL -. "new request arrived → extra pass ↻" .-> UALL + UALL -.->|dead / aborted| DEAD +``` + +**Ledger acquire** — a **separate trace root** (not part of the close flow) that +fetches a missing or correct-prior ledger from peers, retries per peer/timer, and +finishes with a reason-dependent store; `checkAccept` + `tryAdvance` run on **any** +completed acquire: + +```mermaid +flowchart TB + classDef span fill:#0e7490,stroke:#155e75,color:#fff; + classDef plain fill:#334155,stroke:#0f172a,color:#fff; + classDef drop fill:#7f1d1d,stroke:#ef4444,color:#fff; + + HIST(["tryAdvance → doAdvance → fetchForHistory
(Reason::HISTORY; no span)"]):::plain + NEED(["checkAccept / handleNewValidation /
consensus wrong-ledger (no span)"]):::plain + INB(["InboundLedgers::acquire (no span)"]):::plain + ACQ["ledger.acquire
(InboundLedger::init)"]:::span + TRIG(["trigger / addPeers / onTimer
↻ per peer / chunk (no span)"]):::plain + FAILED(["timeouts > 6 → failed_ →
logFailure (NO store, NO checkAccept)"]):::drop + DONE(["done() — complete && !failed (no span)"]):::plain + ONF(["onLedgerFetched (no span)
(HISTORY: no store)"]):::plain + STORE["ledger.store
(GENERIC / CONSENSUS)"]:::span + CACC(["checkAccept + tryAdvance (no span)
(↻ may publish/advance many ledgers)"]):::plain + + NEED -.->|GENERIC / CONSENSUS| INB --> ACQ + HIST -.->|HISTORY| INB + ACQ -.->|not complete| TRIG + TRIG -. "retry ↻" .-> TRIG + TRIG -.->|timeout cap| FAILED + TRIG -.->|complete| DONE + DONE -.->|reason == HISTORY| ONF + DONE -.->|GENERIC / CONSENSUS| STORE + DONE --> CACC + CACC -. "advanceWork ↻ → further HISTORY acquire" .-> HIST +``` + +Side-flow evidence: + +- **Pathfind subscription lifecycle**: `path_find` create inserts a persistent + subscription (`makePathRequest`); `update_all` re-runs each active request every + close, removes dead subscribers (`doAborting` + `remove_if` erase), and takes an + extra pass when a new request arrived mid-run + ([PathRequestManager.cpp:103](../src/xrpld/rpc/detail/PathRequestManager.cpp#L103), + [169](../src/xrpld/rpc/detail/PathRequestManager.cpp#L169), + [181](../src/xrpld/rpc/detail/PathRequestManager.cpp#L181)). +- **Acquire outcome fork**: `timeouts_ > kLedgerTimeoutRetriesMax` (= 6) sets + `failed_` → terminal `logFailure`, no store/checkAccept + ([InboundLedger.cpp:387](../src/xrpld/app/ledger/detail/InboundLedger.cpp#L387)). +- **done() reason branch (store side only)**: `HISTORY` → `onLedgerFetched`, **no** + `storeLedger`; else → `storeLedger`. But `checkAccept` + `tryAdvance` run for + **any** `complete_ && !failed_` acquire regardless of reason + ([InboundLedger.cpp:495](../src/xrpld/app/ledger/detail/InboundLedger.cpp#L495) + store switch; [507](../src/xrpld/app/ledger/detail/InboundLedger.cpp#L507) + reason-independent checkAccept/tryAdvance). +- **tryAdvance multi-ledger loop**: `doAdvance` runs `do { … } while (advanceWork_)`, + publishing a range of ledgers and recursively triggering further HISTORY acquire + ([LedgerMaster.cpp:1905](../src/xrpld/app/ledger/detail/LedgerMaster.cpp#L1905)). + +### Where telemetry parenting differs from protocol flow + +The graph above is protocol control flow. The OpenTelemetry span **parent links** +are built differently and, in several places, do **not** represent a real +call edge. Read a trace with these in mind: + +| Telemetry does this | Real protocol flow | +| ------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `tx.process` is a `hashSpan` root from `txID` — an independent trace root ([TxTracing.h:63](../src/xrpld/telemetry/TxTracing.h#L63)). | The real edge is the synchronous `doSubmit → processTransaction` call; it is **not** a child of `rpc.command.submit`. | +| `tx.preflight` / `tx.preclaim` / `tx.transactor` share one `txID`-derived trace ID. | That shared ID is a correlation trick, not a call edge. The real order is the composed `apply()` at [apply.cpp:118](../src/libxrpl/tx/apply.cpp#L118). They are **not** children of `tx.process` or `tx.apply`. | +| `consensus.round` uses a deterministic trace ID from the previous ledger hash. | This makes **all validators share one trace ID** (a cross-node shared root), not a per-node parent. The real round-to-round edge is `endConsensus → beginConsensus`. | +| `consensus.accept` (main thread) and `consensus.accept.apply` (JtAccept worker) are wired via a captured context. | The real edge is the queued `JtAccept` job, a thread hand-off ([RCLConsensus.cpp:483](../src/xrpld/app/consensus/RCLConsensus.cpp#L483)). | +| `pathfind.update_all` parents nothing from the original `pathfind.request`. | The causal link is the ledger-close job on `JtUpdatePf`, not span nesting. | +| `ledger.acquire` and its downstream `ledger.store` / `ledger.validate`. | Reached via the `AcqDone` job, not parent inheritance; `ledger.acquire` is its own root. | +| `peer.*.receive` (fresh `kConsumer` root) and `consensus.*.receive` on the same message. | Two **sequential stages of one synchronous handler**, not parent/child; on a duplicate/untrusted drop the `consensus.*.receive` is never created. | +| Receive spans adopt the sender's `trace_id` + `span_id` as a genuine cross-node parent. | Deliberate: the receive span becomes a child of a **different node's** span (a cross-node context marker, not an in-process edge). `tx.receive` is asymmetric — it borrows only the sender's `span_id` and re-derives its own `trace_id` from `txID`. | + +> **Known telemetry artifacts** (from live audits, memory `otel-span-hierarchy-audit`): +> an RPC entry span's scope can leak across a reused coroutine worker, and the +> `hashSpan` roots (`tx.*`) — along with plain roots like `ledger.acquire` — can +> surface in Tempo as dangling "root span not yet received". These are +> exporter/parenting artifacts, not real control-flow parents. + +--- + ## Insights and Sample Queries This section shows what questions you can answer using the span attributes, with example Tempo TraceQL queries. @@ -586,12 +1391,20 @@ does not apply to these dimensions. ### Histogram Buckets -Configured in `otel-collector-config.yaml`: +Configured in `otel-collector-config.yaml` (spanmetrics connector, `unit: ms`): ``` -1ms, 5ms, 10ms, 25ms, 50ms, 100ms, 250ms, 500ms, 1s, 5s +1ms, 5ms, 10ms, 25ms, 50ms, 100ms, 250ms, 500ms, 1s, 2s, 3s, 4s, 5s, 10s, 30s ``` +Sub-second boundaries cover RPC/tx/ledger spans; 2s-4s resolve second-scale +consensus spans (`consensus.round`, `consensus.establish`) that would otherwise +pile into one 1s-5s bucket and make `histogram_quantile` a meaningless +interpolation; 10s/30s give the `ledger.acquire` catch-up tail a measurable home. +Boundaries must stay strictly ascending. The native beast::insight histograms +(ms-scale RPC/IO timers) keep the original 1ms-5s buckets in +`Telemetry.cpp` — they never exceed 5s, so they need no high-range buckets. + ## System Metrics (OTel native -- beast::insight) xrpld has a built-in metrics framework (`beast::insight`) that exports metrics natively via OTLP to the OTel Collector. These complement the span-derived RED metrics by providing system-level gauges, counters, and timers that don't map to individual trace spans. @@ -624,7 +1437,7 @@ The `OTelCollector` implementation exports metrics via OTLP/HTTP to the same OTe | `peer_finder_active_inbound_peers` | PeerfinderManager.cpp:214 | Active inbound peer connections | | `peer_finder_active_outbound_peers` | PeerfinderManager.cpp:215 | Active outbound peer connections | | `overlay_peer_disconnects` | OverlayImpl.h:557 | Peer disconnect count | -| `job_count` | JobQueue.cpp:26 | Current job queue depth | +| `jobq_job_count` | JobQueue.cpp:26 | Current job queue depth (all types) | | `jobq_{jobtype}_waiting` | JobTypeData.h | Jobs of this type enqueued but not yet running | | `jobq_{jobtype}_running` | JobTypeData.h | Jobs of this type currently executing | | `jobq_{jobtype}_deferred` | JobTypeData.h | Jobs of this type held back because the type's concurrency limit was hit | @@ -694,29 +1507,30 @@ Limits that matter for ledger sync (JobTypes.h:54-77): These gauges are exported via the OTel Metrics SDK `PeriodicMetricReader` (10s interval), NOT through beast::insight. -| Prometheus Metric | Source | Description | -| --------------------------------------------------- | ------------------- | ------------------------------------------------ | -| `server_info{metric="server_state"}` | MetricsRegistry.cpp | Operating mode (0=DISCONNECTED .. 4=FULL) | -| `server_info{metric="uptime"}` | MetricsRegistry.cpp | Seconds since server start | -| `server_info{metric="peers"}` | MetricsRegistry.cpp | Total connected peers | -| `server_info{metric="validated_ledger_seq"}` | MetricsRegistry.cpp | Validated ledger sequence number | -| `server_info{metric="ledger_current_index"}` | MetricsRegistry.cpp | Current open ledger sequence | -| `server_info{metric="peer_disconnects_resources"}` | MetricsRegistry.cpp | Cumulative resource-related peer disconnects | -| `server_info{metric="last_close_proposers"}` | MetricsRegistry.cpp | Proposers in last closed round | -| `server_info{metric="last_close_converge_time_ms"}` | MetricsRegistry.cpp | Last close convergence time (ms) | -| `build_info{version=""}` | MetricsRegistry.cpp | Info-style metric (always 1) | -| `complete_ledgers{bound="start\|end",index=""}` | MetricsRegistry.cpp | Complete ledger range start/end pairs | -| `db_metrics{metric="db_kb_total"}` | MetricsRegistry.cpp | Total database size (KB) | -| `db_metrics{metric="db_kb_ledger"}` | MetricsRegistry.cpp | Ledger database size (KB) | -| `db_metrics{metric="db_kb_transaction"}` | MetricsRegistry.cpp | Transaction database size (KB) | -| `db_metrics{metric="historical_perminute"}` | MetricsRegistry.cpp | Historical ledger fetches per minute | -| `cache_metrics{metric="AL_size"}` | MetricsRegistry.cpp | AcceptedLedger cache size | -| `nodestore_state{metric="node_reads_duration_us"}` | MetricsRegistry.cpp | Cumulative read time (microseconds) | -| `nodestore_state{metric="node_writes_duration_us"}` | MetricsRegistry.cpp | Cumulative write time (microseconds) | -| `nodestore_state{metric="read_request_bundle"}` | MetricsRegistry.cpp | Read request bundle count | -| `nodestore_state{metric="read_threads_running"}` | MetricsRegistry.cpp | Active read threads | -| `nodestore_state{metric="read_threads_total"}` | MetricsRegistry.cpp | Total read threads configured | -| `rpc_in_flight_requests` | PerfLogImp.cpp | RPC requests currently executing (UpDownCounter) | +| Prometheus Metric | Source | Description | +| --------------------------------------------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `server_info{metric="server_state"}` | MetricsRegistry.cpp | Operating mode (0=DISCONNECTED .. 4=FULL) | +| `server_info{metric="uptime"}` | MetricsRegistry.cpp | Seconds since server start | +| `server_info{metric="peers"}` | MetricsRegistry.cpp | Total connected peers | +| `server_info{metric="validated_ledger_seq"}` | MetricsRegistry.cpp | Validated ledger sequence number | +| `server_info{metric="ledger_current_index"}` | MetricsRegistry.cpp | Current open ledger sequence | +| `server_info{metric="peer_disconnects_resources"}` | MetricsRegistry.cpp | Cumulative resource-related peer disconnects | +| `server_info{metric="last_close_proposers"}` | MetricsRegistry.cpp | Proposers in last closed round | +| `server_info{metric="last_close_converge_time_ms"}` | MetricsRegistry.cpp | Last close convergence time (ms) | +| `server_info{metric="last_close_time"}` | MetricsRegistry.cpp | Network close time of last closed ledger (NetClock secs since XRPL epoch). Age = `time() - (value + 946684800)`; close interval = `1/rate(ledgers_closed_total)`, not a gauge delta | +| `build_info{version=""}` | MetricsRegistry.cpp | Info-style metric (always 1) | +| `complete_ledgers{bound="start\|end",index=""}` | MetricsRegistry.cpp | Complete ledger range start/end pairs | +| `db_metrics{metric="db_kb_total"}` | MetricsRegistry.cpp | Total database size (KB) | +| `db_metrics{metric="db_kb_ledger"}` | MetricsRegistry.cpp | Ledger database size (KB) | +| `db_metrics{metric="db_kb_transaction"}` | MetricsRegistry.cpp | Transaction database size (KB) | +| `db_metrics{metric="historical_perminute"}` | MetricsRegistry.cpp | Historical ledger fetches per minute | +| `cache_metrics{metric="AL_size"}` | MetricsRegistry.cpp | AcceptedLedger cache size | +| `nodestore_state{metric="node_reads_duration_us"}` | MetricsRegistry.cpp | Cumulative read time (microseconds) | +| `nodestore_state{metric="node_writes_duration_us"}` | MetricsRegistry.cpp | Cumulative write time (microseconds) | +| `nodestore_state{metric="read_request_bundle"}` | MetricsRegistry.cpp | Read request bundle count | +| `nodestore_state{metric="read_threads_running"}` | MetricsRegistry.cpp | Active read threads | +| `nodestore_state{metric="read_threads_total"}` | MetricsRegistry.cpp | Total read threads configured | +| `rpc_in_flight_requests` | PerfLogImp.cpp | RPC requests currently executing (UpDownCounter) | #### Sync Diagnosis Signals @@ -945,18 +1759,29 @@ Multiple xrpld instances can send telemetry to per-tier collectors that all forward to one Grafana stack. Four resource attributes segregate the data so one dashboard set serves every deployment: -| Dimension | Attribute | Set by | Example values | -| ----------- | ------------------------ | ---------- | ------------------------------ | -| Node | `service.instance.id` | xrpld cfg | `alice-laptop`, `ci-runner-7` | -| Service | `service.name` | xrpld cfg | `xrpld`, `xrpld-validator` | -| Network | `xrpl.network.type` | xrpld node | `mainnet`, `testnet`, `devnet` | -| Environment | `deployment.environment` | collector | `local`, `test`, `ci`, `prod` | +| Dimension | Attribute | Set by | Example values | +| ----------- | ------------------------ | ---------- | ------------------------------------------------ | +| Node | `service.instance.id` | xrpld cfg | `alice-laptop`, `ci-runner-7` | +| Service | `service.name` | xrpld cfg | `xrpld`, `xrpld-validator` | +| Network | `xrpl.network.type` | xrpld node | `mainnet`, `testnet`, `devnet`, `perf` | +| Environment | `deployment.environment` | collector | `local`, `test`, `ci`, `prod` | +| Work Item | `xrpl.work.item` | perf-iac | `RIPD-7455` (empty outside perf runs) | +| Branch | `xrpl.branch` | perf-iac | `baseline::`, `test::` | +| Node Role | `xrpl.node.role` | perf-iac | `validator`, `peer` | Dashboards expose these as the template variables `$node`, `$service_name`, -`$xrpl_network_type`, and `$deployment_environment` (each variable name -matches its Prometheus label). Select them top-down — environment → network -→ service → node. Selecting **All** matches every value, including series -lacking the label, so mixed old/new data never disappears. +`$xrpl_network_type`, `$deployment_environment`, `$xrpl_work_item`, +`$xrpl_branch`, and `$xrpl_node_role` (each variable name matches its +Prometheus label). Select them top-down — work item → branch → node role → +node for a perf comparison run, or environment → network → service → node for +general use. Selecting **All** matches every value, including series lacking +the label, so mixed old/new data never disappears. + +The last three (`$xrpl_work_item`, `$xrpl_branch`, `$xrpl_node_role`) are +populated only during perf-iac comparison runs, which stamp them as resource +attributes from their own alloy pipeline. Outside those runs the labels are +absent; leaving the filters on **All** keeps every dashboard rendering +normally. ### Who owns which attribute @@ -1025,7 +1850,15 @@ three signals' attributes over OTLP directly. ## Grafana Dashboards -Ten dashboards are pre-provisioned in `docker/telemetry/grafana/dashboards/`: +Fifteen dashboards are pre-provisioned in `docker/telemetry/grafana/dashboards/`. +Fourteen are Prometheus-backed; `log-derived-insights` is the only Loki/LogQL +board and is documented last, together with the LogQL-specific traps it exposed. + +> Nine dashboards have a reference section below. `fee-market`, `job-queue`, +> `ledger-data-sync`, `overlay-traffic-detail`, `peer-quality`, and +> `validator-health` are provisioned but not yet documented here — their panel +> descriptions carry the same six-heading reference format, so open the panel +> info icon in Grafana until a section is written. ### RPC Performance (`rpc-performance`) @@ -1056,31 +1889,32 @@ Ten dashboards are pre-provisioned in `docker/telemetry/grafana/dashboards/`: ### Consensus Health (`consensus-health`) -| Panel | Type | PromQL | Labels Used | -| ----------------------------- | ---------- | --------------------------------------------------------------------------- | ---------------- | -| Consensus Round Duration | timeseries | `histogram_quantile(0.95 / 0.50, ... {span_name="consensus.accept"})` | — | -| Consensus Proposals Sent Rate | timeseries | `rate(span_calls_total{span_name="consensus.proposal.send"}[5m])` | — | -| Ledger Close Duration | timeseries | `histogram_quantile(0.95, ... {span_name="consensus.ledger_close"})` | — | -| Validation Send Rate | stat | `rate(span_calls_total{span_name="consensus.validation.send"}[5m])` | — | -| Ledger Apply Duration | timeseries | `histogram_quantile(0.95 / 0.50, ... {span_name="consensus.accept.apply"})` | — | -| Close Time Agreement | timeseries | `rate(span_calls_total{span_name="consensus.accept.apply"}[5m])` | — | -| Consensus Mode Over Time | timeseries | `consensus.ledger_close` by `consensus_mode` | `consensus_mode` | -| Accept vs Close Rate | timeseries | `consensus.accept` vs `consensus.ledger_close` rate | — | -| Validation vs Close Rate | timeseries | `consensus.validation.send` vs `consensus.ledger_close` | — | -| Accept Duration Heatmap | heatmap | `consensus.accept` histogram buckets | `le` | +| Panel | Type | PromQL | Labels Used | +| ----------------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | +| Consensus Round Duration | timeseries | `histogram_quantile(0.95 / 0.50, ... {span_name="consensus.accept"})` | — | +| Consensus Proposals Sent Rate | timeseries | `rate(span_calls_total{span_name="consensus.proposal.send"}[5m])` | — | +| Ledger Close Duration | timeseries | `histogram_quantile(0.95, ... {span_name="consensus.round"})` (full round, not `consensus.ledger_close` which is only the sub-ms onClose prologue) | `consensus_mode` | +| Validation Send Rate | stat | `rate(span_calls_total{span_name="consensus.validation.send"}[5m])` | — | +| Ledger Apply Duration | timeseries | `histogram_quantile(0.95 / 0.50, ... {span_name="consensus.accept.apply"})` | — | +| Close Time Agreement | timeseries | `rate(span_calls_total{span_name="consensus.accept.apply"}[5m])` | — | +| Consensus Mode Over Time | timeseries | `consensus.ledger_close` by `consensus_mode` | `consensus_mode` | +| Accept vs Close Rate | timeseries | `consensus.accept` vs `consensus.ledger_close` rate | — | +| Validation vs Close Rate | timeseries | `consensus.validation.send` vs `consensus.ledger_close` | — | +| Accept Duration Heatmap | heatmap | `consensus.accept` histogram buckets | `le` | ### Ledger Operations (`ledger-operations`) -| Panel | Type | PromQL | Labels Used | -| ----------------------- | ---------- | ---------------------------------------------- | ----------- | -| Ledger Build Rate | stat | `ledger.build` call rate | — | -| Ledger Build Duration | timeseries | p95/p50 of `ledger.build` | — | -| Ledger Validation Rate | stat | `ledger.validate` call rate | — | -| Build Duration Heatmap | heatmap | `ledger.build` histogram buckets | `le` | -| TX Apply Duration | timeseries | p95/p50 of `tx.apply` | — | -| TX Apply Rate | timeseries | `tx.apply` call rate | — | -| Ledger Store Rate | stat | `ledger.store` call rate | — | -| Build vs Close Duration | timeseries | p95 `ledger.build` vs `consensus.ledger_close` | — | +| Panel | Type | PromQL | Labels Used | +| --------------------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------- | ----------- | +| Ledger Build Rate | stat | `ledger.build` call rate | — | +| Ledger Build Duration | timeseries | p95/p50 of `ledger.build` | — | +| Ledger Validation Rate | stat | `ledger.validate` call rate | — | +| Build Duration Heatmap | heatmap | `ledger.build` histogram buckets | `le` | +| TX Apply Duration | timeseries | p95/p50 of `tx.apply` | — | +| TX Apply Rate | timeseries | `tx.apply` call rate | — | +| Ledger Store Rate | stat | `ledger.store` call rate | — | +| Build vs Close Duration | timeseries | p95 `ledger.build` vs `consensus.round` (full round, not `consensus.ledger_close` which is only the sub-ms onClose prologue) | — | +| Ledger Close Interval & Age | timeseries | Interval: `1/rate(ledgers_closed_total)`; Age: `time() - (server_info{metric="last_close_time"} + 946684800)` | — | ### Peer Network (`peer-network`) @@ -1095,47 +1929,57 @@ Requires `trace_peer=1` in the `[telemetry]` config section. ### Node Health -- System Metrics (`node-health`) -| Panel | Type | PromQL | Labels Used | -| -------------------------------- | ---------- | --------------------------------------------------------------------------------- | ---------------- | -| Validated Ledger Age | stat | `ledgermaster_validated_ledger_age` | — | -| Published Ledger Age | stat | `ledgermaster_published_ledger_age` | — | -| Operating Mode (Time Share) | timeseries | `rate(state_accounting_X_duration) / sum(rate(all modes))` | — | -| Operating Mode Transitions | timeseries | `state_accounting_*_transitions` | — | -| I/O Latency | timeseries | `histogram_quantile(0.95, ios_latency_bucket)` | — | -| Job Queue Depth | timeseries | `job_count` | — | -| Ledger Fetch Rate | stat | `rate(ledger_fetches[5m])` | — | -| Ledger History Mismatches | stat | `rate(ledger_history_mismatch[5m])` | — | -| Key Jobs Execution Time | timeseries | `acceptledger{quantile="$quantile"}` (+ 10 more key jobs) | `quantile` | -| Key Jobs Dequeue Wait Time | timeseries | `acceptledger_q{quantile="$quantile"}` (+ 10 more) | `quantile` | -| FullBelowCache Size | timeseries | `node_family_full_below_cache_size` | — | -| FullBelowCache Hit Rate | gauge | `node_family_full_below_cache_hit_rate` | — | -| Ledger Publish Gap | stat | `Published_Ledger_Age - Validated_Ledger_Age` | — | -| State Duration Rate (All States) | timeseries | `rate(state_accounting__duration[5m]) / 1000000` | — | -| All Jobs Execution Time (Detail) | timeseries | `histogram_quantile($quantile, rate(job_running_us_bucket[5m])) by job_type` — µs | `quantile` | -| All Jobs Dequeue Wait (Detail) | timeseries | `histogram_quantile($quantile, rate(job_queued_us_bucket[5m])) by job_type` — µs | `quantile` | -| Server State | stat | `server_info{metric="server_state"}` | `metric` | -| Uptime | stat | `server_info{metric="uptime"}` | `metric` | -| Peer Count | stat | `server_info{metric="peers"}` | `metric` | -| Validated Ledger Seq | stat | `server_info{metric="validated_ledger_seq"}` | `metric` | -| Build Version | stat | `build_info` | `version` | -| Complete Ledger Ranges | table | `complete_ledgers` | `bound`, `index` | -| Database Sizes | timeseries | `db_metrics{metric=~"db_kb_.*"}` | `metric` | -| Historical Fetch Rate | stat | `db_metrics{metric="historical_perminute"}` | `metric` | +| Panel | Type | PromQL | Labels Used | +| -------------------------------------- | ---------- | ---------------------------------------------------------- | ---------------- | +| Validated Ledger Age | stat | `ledgermaster_validated_ledger_age` | — | +| Published Ledger Age | stat | `ledgermaster_published_ledger_age` | — | +| Operating Mode (Time Share) | timeseries | `rate(state_accounting_X_duration) / sum(rate(all modes))` | — | +| Operating Mode Transitions | timeseries | `state_accounting_*_transitions` | — | +| I/O Latency | timeseries | `histogram_quantile(0.95, ios_latency_bucket)` | — | +| Job Queue Depth | timeseries | `jobq_job_count` | — | +| Ledger Fetch Rate | stat | `rate(ledger_fetches[5m])` | — | +| Ledger History Mismatches | stat | `rate(ledger_history_mismatch[5m])` | — | +| Key Jobs Execution Time | timeseries | `acceptledger{quantile="$quantile"}` (+ 10 more key jobs) | `quantile` | +| Key Jobs Dequeue Wait Time | timeseries | `acceptledger_q{quantile="$quantile"}` (+ 10 more) | `quantile` | +| FullBelowCache Size | timeseries | `node_family_full_below_cache_size` | — | +| FullBelowCache Hit Rate | gauge | `node_family_full_below_cache_hit_rate` | — | +| Ledger Publish Gap | stat | `Published_Ledger_Age - Validated_Ledger_Age` | — | +| State Duration Rate (Full vs Tracking) | timeseries | `rate(state_accounting_full_duration[5m]) / 1000000` | — | +| All Jobs Execution Time (Detail) | timeseries | `{__name__=~"", quantile="$quantile"}` | `quantile` | +| All Jobs Dequeue Wait (Detail) | timeseries | `{__name__=~"_q", quantile="$quantile"}` | `quantile` | +| Server State | stat | `server_info{metric="server_state"}` | `metric` | +| Uptime | stat | `server_info{metric="uptime"}` | `metric` | +| Peer Count | stat | `server_info{metric="peers"}` | `metric` | +| Validated Ledger Seq | stat | `server_info{metric="validated_ledger_seq"}` | `metric` | +| Build Version | stat | `build_info` | `version` | +| Complete Ledger Ranges | table | `complete_ledgers` | `bound`, `index` | +| Database Sizes | timeseries | `db_metrics{metric=~"db_kb_.*"}` | `metric` | +| Historical Fetch Rate | stat | `db_metrics{metric="historical_perminute"}` | `metric` | ### Network Traffic -- System Metrics (`network-traffic`) -| Panel | Type | PromQL | Labels Used | -| ------------------------------------ | ---------- | ------------------------------------------------------ | ----------- | -| Active Peers | timeseries | `peer_finder_active_*_peers` | — | -| Peer Disconnects | timeseries | `increase(overlay_peer_disconnects[$__rate_interval])` | — | -| Total Network Bytes | timeseries | `rate(total_bytes_in/out[$__rate_interval])` | — | -| Total Network Messages | timeseries | `rate(total_messages_in/out[$__rate_interval])` | — | -| Transaction Traffic | timeseries | `rate(transactions_messages_in/out[$__rate_interval])` | — | -| Proposal Traffic | timeseries | `rate(proposals_messages_in/out[$__rate_interval])` | — | -| Validation Traffic | timeseries | `rate(validations_messages_in/out[$__rate_interval])` | — | -| Traffic by Category | bargauge | `topk(10, rate(*_bytes_in[$__rate_interval]))` | — | -| Duplicate Traffic (Wasted Bandwidth) | timeseries | `rate(*_duplicate_bytes_in/out[$__rate_interval])` | — | -| All Traffic Categories (Detail) | timeseries | `topk(15, rate(*_bytes_in[$__rate_interval]))` | — | +| Panel | Type | PromQL | Labels Used | +| ------------------------------------ | ---------- | -------------------------------------------------------------------------------------------------------------------------- | ----------- | +| Active Peers | timeseries | `peer_finder_active_*_peers` | — | +| Peer Disconnects | timeseries | `increase(overlay_peer_disconnects[$__rate_interval])` | — | +| Total Network Bytes | timeseries | `rate(total_bytes_in/out[$__rate_interval])` | — | +| Total Network Messages | timeseries | `rate(total_messages_in/out[$__rate_interval])` | — | +| Transaction Traffic | timeseries | `rate(transactions_messages_in/out[$__rate_interval])` | — | +| Proposal Traffic | timeseries | `rate(proposals_messages_in/out[$__rate_interval])` | — | +| Validation Traffic | timeseries | `rate(validations_messages_in/out[$__rate_interval])` | — | +| Traffic by Category | bargauge | `topk(10, label_replace(sum by (service_instance_id)(rate([$__rate_interval])),"__name__","","","") or …)` | — | +| Duplicate Traffic (Wasted Bandwidth) | timeseries | `rate(*_duplicate_bytes_in/out[$__rate_interval])` | — | +| All Traffic Categories (Detail) | timeseries | `topk(15, label_replace(sum by (service_instance_id)(rate([$__rate_interval])),"__name__","","","") or …)` | — | + +> **Why the per-category panels enumerate each metric.** A bare +> `rate({__name__=~".*_bytes_in"}[…])` fails on Mimir/Cloud with _"vector +> cannot contain metrics with the same labelset"_: `rate()` drops the +> `__name__` label, so the many matched counters collapse to identical +> labelsets. Wrapping in `sum by (__name__, …)` does **not** help (the inner +> vector is rejected before the outer `sum`). The working form enumerates each +> `*_bytes_in` metric and re-attaches its name with `label_replace(..., +"__name__", "", "", "")`, so the existing `{{__name__}}` legend and +> the per-series display-name overrides keep working. ### RPC & Pathfinding -- System Metrics (`rpc-pathfinding`) @@ -1530,6 +2374,12 @@ The receiver tails `/var/log/xrpld/*/debug.log` inside the collector container. The OTel Collector emits logs to Loki with `service_name="xrpld"` (not `job="xrpld"`). +For log-derived panels built on these queries, see the +[Log-Derived Insights](#log-derived-insights-log-derived-insights) dashboard and its +LogQL trap list — `partition`, `severity`, and `xrpl_network_type` are +**structured metadata**, not stream labels, so they must be filtered with `|` +after the selector and cannot be discovered by `label_values()`. + ```logql # Find all logs for a specific trace {service_name="xrpld"} |= "trace_id=abc123def456789012345678abcdef01" @@ -1537,11 +2387,13 @@ The OTel Collector emits logs to Loki with `service_name="xrpld"` (not `job="xrp # Error logs with trace context (log lines with ERR severity that have a trace_id) {service_name="xrpld"} |= "ERR" |= "trace_id=" -# All logs from a specific partition that were emitted during a span -{service_name="xrpld"} |= "LedgerMaster" | regexp `trace_id=(?P[a-f0-9]+)` | trace_id != "" +# All logs from a specific partition that were emitted during a span. +# Prefer the structured-metadata filter over a line match: `|= "LedgerMaster"` +# also matches the substring anywhere in the message body. +{service_name="xrpld"} | partition = `LedgerMaster` | trace_id != "" # Logs from a specific subsystem during a span (e.g. LedgerConsensus) -{service_name="xrpld"} |= "LedgerConsensus" |= "trace_id=" +{service_name="xrpld"} | partition = `LedgerConsensus` | trace_id != "" # Logs from the last hour containing trace context {service_name="xrpld"} |= "trace_id=" | regexp `(?P\S+):(?P\S+)\s+trace_id=(?P[a-f0-9]+)` @@ -1558,6 +2410,120 @@ count_over_time({service_name="xrpld"} |= "trace_id=" [5m]) 4. Open Grafana at http://localhost:3000 -> Explore -> Loki and search for `{service_name="xrpld"} |= "trace_id="`. 5. Click the TraceID link to navigate to the corresponding trace in Tempo. +### Log-Derived Insights (`log-derived-insights`) + +The only **Loki/LogQL** dashboard. It surfaces detail that no metric or span +records, by parsing `debug.log` text. 41 panels in 10 rows: 8 stat, 18 +timeseries, 2 table, 1 state-timeline, 1 logs, 1 text, across 35 queries. + +> **REQUIRES DEBUG LOGS for most rows.** xrpld's default threshold is `Info` +> (`Severity thresh = Severity::Info`, `app/main/Main.cpp`). Rows tagged `[DBG]` +> read `DBG`-severity lines that a default node never writes, so those panels are +> **empty** on an unmodified node — and an empty panel means _not collecting_, not +> _no problem_. Rows tagged `[DEFAULT OK]` work as shipped. +> +> Enable per partition rather than globally (`Resource` alone emits ~329k +> lines/6h): +> +> ``` +> log_level ManifestCache debug +> log_level Resource debug +> log_level InboundLedger debug +> log_level Peer debug +> log_level PeerFinder debug +> ``` +> +> Those five cover every `[DBG]` row. The `[MIXED]` stat row additionally +> reads `LedgerConsensus` and `LoadMonitor`, both of which already emit at +> the default level, so its error/consensus/breach/sync panels populate +> without any change — only its manifest, fee, and fetch-waste panels need +> debug enabled. + +| Row | Gate | Key panels | +| --------------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Worst Offenders — Node Ranking | `[MIXED]` | 8 stat panels ranking nodes by error volume, attack-like input, total fee charged, manifest rejections, consensus problems, job latency breaches, sync instability, and ledger fetch waste | +| Node Operating State Transitions | `[DEFAULT OK]` | Transition rate and state timeline from `STATE->` (`NetworkOPsImp::setMode`, info) | +| Log Volume & Severity Mix | `[DEFAULT OK]` | Line rate by severity; top-N partitions by rate | +| Manifests — Disposition & Producers | `[DBG]` | Disposition rate; accept-vs-reject; top-N master keys | +| Resource Fee Charges — Load Attribution | `[DBG]` | Charge rate by reason; fee-weighted load; top-N peers by IP and public key | +| Ledger Acquisition Efficiency | `[DBG]` | Duplicate ratio; good vs duplicate vs timeout | +| Peer Lifecycle & Disconnects | `[DBG]` | Disconnect reason breakdown; handshake and accept rate | +| Consensus Phase & Mode | `[DEFAULT OK]` | Phase transitions; operating-mode proxy; quorum and trusted-set size | +| Slow Job Latency Breaches | `[DEFAULT OK]` | Run p99, wait p99, breach rate by job (`LoadMonitor`, >500ms only) | +| Error & Warning Stream | `[DEFAULT OK]` | WRN/ERR/FTL rate by partition; live log tail | + +Filters: `$service_name`, `$deployment_environment`, `$node`, +`$xrpl_network_type`, `$severity`, plus log-derived `$consensus_phase`, +`$consensus_mode`, `$manifest_action`, `$charge_reason`, and `$topn`. + +#### LogQL traps this dashboard exposed + +Ten mistakes that fail **silently** — each cost a debugging cycle, so check them +before adding any LogQL panel. + +1. **`partition` is structured metadata, not a stream label.** + `{service_name="xrpld", partition="ManifestCache"}` returns **zero rows with + no error**. Correct form: `{service_name="xrpld"} | partition = \`ManifestCache\``. +Stream labels are only `service_name`, `service_instance_id`, +`deployment_environment`. Everything else — `partition`, `severity`, +`xrpl_network_type`, `message`, `trace_id` — is structured metadata. + +2. **`label_values()` cannot see structured metadata.** A `query`-type template + variable over `xrpl_network_type`, `severity`, or `partition` returns an empty + dropdown; only true stream labels populate. Use a `custom` variable with + enumerated values instead. This is why filters appeared blank. + +3. **A target with no datasource `uid` resolves to the DEFAULT datasource.** + The Prometheus dashboards use `{"type": "prometheus"}` with no uid and work + only because Prometheus _is_ the default. A Loki target written the same way + sends LogQL to Prometheus and returns nothing. Always pin + `{"type": "loki", "uid": "${DS_LOKI}"}`. + +4. **`$__rate_interval` is Prometheus-only — Loki panels must use `[$__auto]`.** + Grafana does not substitute `$__rate_interval` for a Loki target, so Loki + receives the literal string and fails with + `parse error: not a valid duration string: "$__rate_interval"`, which surfaces + as "No data". The other 14 dashboards all use `$__rate_interval` because they + are Prometheus-backed; do **not** align LogQL panels to that convention. + +5. **Loki caps a query at 2000 series.** Any per-key or per-IP aggregation must be + wrapped in `topk(N, ...)` or it fails with HTTP 400. A true distinct-key count + over a large key space is therefore not possible in a panel. + +6. **Loki tables need `labelsToFields` plus `reduce`.** Loki attaches labels to + the Value field instead of returning columns, so a table panel renders bare + Time/Value without `labelsToFields`. Grafana also runs a Loki table target as a + **range** query even when `instant: true` is set, producing one row per series + _per timestamp_ — visible as the same key repeated many times. Use + `reduce(lastNotNull, labelsToFields)` then `organize`, and note the value + column is then named `Last *`, which any field override must match. + +7. **Title Case legends need `label_format`, not value mappings.** A label-driven + legend renders the raw log value (`full`, `moderate peer request`). Grafana + value mappings do not help — they map the metric _value_, not label text in + `displayName`. Rewrite the label in the query: + `| label_format state=\`{{if eq .state "full"}}Full{{else}}{{.state}}{{end}}\``. + +8. **`unwrap` must be the last pipeline stage.** Any label filter or + `label_format` placed after `| unwrap ` makes the query invalid and it + returns zero frames. + +9. **Non-matching lines yield an empty label.** A line in the selected partition + that does not match the panel's `regexp` still passes through with an empty + extracted label, which renders as a blank legend entry. Guard with + `|