From c364ebbbeb90c4dc5cb1d98f4928033310c15a02 Mon Sep 17 00:00:00 2001 From: Sophia Xie Date: Mon, 6 Jul 2026 16:11:12 -0700 Subject: [PATCH] fix: Persist RAM-only clean nodes during online-delete rotation fix: Log when a clean node missing from both backends is re-stored Addional guardrails and logging --- src/xrpld/app/misc/SHAMapStoreImp.cpp | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/xrpld/app/misc/SHAMapStoreImp.cpp b/src/xrpld/app/misc/SHAMapStoreImp.cpp index 3f4bd6280f..a61b31f859 100644 --- a/src/xrpld/app/misc/SHAMapStoreImp.cpp +++ b/src/xrpld/app/misc/SHAMapStoreImp.cpp @@ -15,9 +15,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -252,8 +254,23 @@ bool SHAMapStoreImp::copyNode(std::uint64_t& nodeCount, SHAMapTreeNode const& node) { // Copy a single record from node to dbRotating_ - dbRotating_->fetchNodeObject( + auto obj = dbRotating_->fetchNodeObject( node.getHash().asUInt256(), 0, NodeStore::FetchType::Synchronous, true); + if (!obj) + { + // Reachable from the validated state map in memory, but present in + // neither backend: its only on-disk copy lived in a backend removed by + // an earlier rotation, and it was never rewritten because it is clean + // (cowid == 0, so flushDirty skips it). Persist the in-memory body + // directly into the writable backend so it survives this rotation + // instead of later surfacing as an unresolvable SHAMapMissingNode. + auto const hash = node.getHash().asUInt256(); + Serializer s; + node.serializeWithPrefix(s); + dbRotating_->store(NodeObjectType::AccountNode, std::move(s.modData()), hash, 0); + JLOG(journal_.warn()) << "copyNode: re-stored node missing from both backends, hash=" + << hash << " type=" << static_cast(node.getType()); + } if ((++nodeCount % checkHealthInterval_) == 0u) { if (healthWait() == HealthResult::Stopping)