From 943c92ca1ec7d84bcca6bac37dec2efa042fcb64 Mon Sep 17 00:00:00 2001 From: Vito <5780819+Tapanito@users.noreply.github.com> Date: Thu, 4 Jun 2026 15:27:22 +0200 Subject: [PATCH] perf: Dispatch hasInvalidAmount on type tag instead of dynamic_cast hasInvalidAmount runs on the per-transaction invariant-checking path (ValidAmounts::finalize) and at preflight, walking every field of every modified entry. The previous implementation tried a dynamic_cast chain (STAmount, STObject, STArray) on each field, so every scalar non-amount field paid three failed RTTI casts. Dispatch on the serialized type tag via getSType() and a switch instead. The object-like tags all denote STObject subclasses (STLedgerEntry, STTx), so the downcast is sound; safeDowncast keeps a dynamic_cast validity assert in debug builds while compiling to static_cast in release. --- src/libxrpl/protocol/STAmount.cpp | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/libxrpl/protocol/STAmount.cpp b/src/libxrpl/protocol/STAmount.cpp index 1ba9cd042f..70000ba7d9 100644 --- a/src/libxrpl/protocol/STAmount.cpp +++ b/src/libxrpl/protocol/STAmount.cpp @@ -1250,16 +1250,30 @@ hasInvalidAmount(STBase const& field, int depth, beast::Journal j) return true; } - if (auto const amount = dynamic_cast(&field)) - return !isLegalMPT(*amount) || !isLegalNet(*amount); + // Dispatch on the serialized type tag rather than RTTI: this is on the invariant-checking path + // and a dynamic_cast chain over every field of every modified entry is measurably expensive. + // The object-like tags below all denote STObject subclasses (STLedgerEntry, STTx), so the + // downcast is sound; nested fields are only ever plain STI_OBJECT / STI_ARRAY containers. + // safeDowncast keeps a dynamic_cast validity assert in debug builds while compiling to + // static_cast in release. + switch (field.getSType()) + { + case STI_AMOUNT: { + auto const& amount = safeDowncast(field); + return !isLegalMPT(amount) || !isLegalNet(amount); + } - if (auto const object = dynamic_cast(&field)) - return hasInvalidAmount(*object, depth + 1, j); + case STI_OBJECT: + case STI_LEDGERENTRY: + case STI_TRANSACTION: + return hasInvalidAmount(safeDowncast(field), depth + 1, j); - if (auto const array = dynamic_cast(&field)) - return hasInvalidAmount(*array, depth + 1, j); + case STI_ARRAY: + return hasInvalidAmount(safeDowncast(field), depth + 1, j); - return false; + default: + return false; + } } bool