diff --git a/src/xrpld/app/misc/SHAMapStoreImp.cpp b/src/xrpld/app/misc/SHAMapStoreImp.cpp index aec5d503f5..fa4de7ba21 100644 --- a/src/xrpld/app/misc/SHAMapStoreImp.cpp +++ b/src/xrpld/app/misc/SHAMapStoreImp.cpp @@ -262,6 +262,45 @@ SHAMapStoreImp::fdRequired() const return fdRequired_; } +void +SHAMapStoreImp::rescueNode(SHAMapTreeNode const& node) +{ + XRPL_ASSERT(node.cowid() == 0, "SHAMapStoreImp::copyNode : rescued node must be clean"); + // 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 nodeType = node.getType(); + auto const objectType = std::invoke([nodeType] { + switch (nodeType) + { + case SHAMapNodeType::TnAccountState: + return NodeObjectType::AccountNode; + case SHAMapNodeType::TnTransactionNm: + return NodeObjectType::TransactionNode; + default: + return NodeObjectType::Unknown; + } + }); + + auto const hash = node.getHash().asUInt256(); + if (objectType == NodeObjectType::Unknown) + { + JLOG(journal_.warn()) << "copyNode: unable to re-store node with unknown type, hash=" + << hash << " type=" << static_cast(nodeType); + return; + } + Serializer s; + node.serializeWithPrefix(s); + dbRotating_->store(objectType, std::move(s.modData()), hash, 0); + + JLOG(journal_.warn()) << "copyNode: re-stored node missing from both backends, hash=" << hash + << " type=" << static_cast(nodeType); +} + bool SHAMapStoreImp::copyNode(std::uint64_t& nodeCount, SHAMapTreeNode const& node) { @@ -270,24 +309,7 @@ SHAMapStoreImp::copyNode(std::uint64_t& nodeCount, SHAMapTreeNode const& node) node.getHash().asUInt256(), 0, NodeStore::FetchType::Synchronous, true); if (!obj) { - XRPL_ASSERT(node.cowid() == 0, "SHAMapStoreImp::copyNode : rescued node must be clean"); - // 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); - { - auto const& cached = treeNodeCache_->fetch(hash); - JLOG(journal_.warn()) << "copyNode: re-stored node missing from both backends, hash=" - << hash << " type=" << static_cast(node.getType()) - << ". Node is " << (cached ? "" : "not ") - << "in the tree node cache"; - } + rescueNode(node); } if ((++nodeCount % checkHealthInterval_) == 0u) { @@ -648,8 +670,7 @@ SHAMapStoreImp::freshenCaches() { if (freshenCache(*treeNodeCache_)) return; - if (freshenCache(app_.getMasterTransaction().getCache())) - return; + freshenCache(app_.getMasterTransaction().getCache()); } void diff --git a/src/xrpld/app/misc/SHAMapStoreImp.h b/src/xrpld/app/misc/SHAMapStoreImp.h index e02f64f56f..9090aadd63 100644 --- a/src/xrpld/app/misc/SHAMapStoreImp.h +++ b/src/xrpld/app/misc/SHAMapStoreImp.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -177,6 +178,9 @@ public: minimumOnline() const override; private: + // Force write a node to the writable backend during rotation so it doesn't get lost + void + rescueNode(SHAMapTreeNode const& node); // callback for visitNodes bool copyNode(std::uint64_t& nodeCount, SHAMapTreeNode const& node); @@ -196,7 +200,18 @@ private: for (auto const& key : cache.getKeys()) { - dbRotating_->fetchNodeObject(key, 0, NodeStore::FetchType::Synchronous, true); + [[maybe_unused]] + auto const obj = + dbRotating_->fetchNodeObject(key, 0, NodeStore::FetchType::Synchronous, true); + if constexpr (std::derived_from) + { + if (!obj) + { + auto const node = cache.fetch(key); + if (node) + rescueNode(*node); + } + } if (!(++check % checkHealthInterval_) && healthWait() == HealthResult::Stopping) return true; }