#include #include // IWYU pragma: keep #include // IWYU pragma: keep #include #include #include #include // IWYU pragma: keep #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace xrpl { [[nodiscard]] intr_ptr::SharedPtr makeTypedLeaf(SHAMapNodeType type, boost::intrusive_ptr item, std::uint32_t owner) { if (type == SHAMapNodeType::TnTransactionNm) return intr_ptr::makeShared(std::move(item), owner); if (type == SHAMapNodeType::TnTransactionMd) return intr_ptr::makeShared(std::move(item), owner); if (type == SHAMapNodeType::TnAccountState) return intr_ptr::makeShared(std::move(item), owner); logicError( "Attempt to create leaf node of unknown type " + std::to_string(static_cast>(type))); } SHAMap::SHAMap(SHAMapType t, Family& f) : f_(f), journal_(f.journal()), state_(SHAMapState::Modifying), type_(t) { root_ = intr_ptr::makeShared(cowid_); } // The `hash` parameter is unused. It is part of the interface so it's clear // from the parameters that this is the constructor to use when the hash is // known. The fact that the parameter is unused is an implementation detail that // should not change the interface. SHAMap::SHAMap(SHAMapType t, uint256 const& hash, Family& f) : f_(f), journal_(f.journal()), state_(SHAMapState::Synching), type_(t) { root_ = intr_ptr::makeShared(cowid_); } SHAMap::SHAMap(SHAMap const& other, bool isMutable) : f_(other.f_) , journal_(other.f_.journal()) , cowid_(other.cowid_ + 1) , ledgerSeq_(other.ledgerSeq_) , root_(other.root_) , state_(isMutable ? SHAMapState::Modifying : SHAMapState::Immutable) , type_(other.type_) , backed_(other.backed_) { // If either map may change, they cannot share nodes if ((state_ != SHAMapState::Immutable) || (other.state_ != SHAMapState::Immutable)) { unshare(); } } std::shared_ptr SHAMap::snapShot(bool isMutable) const { return std::make_shared(*this, isMutable); } void SHAMap::dirtyUp( SharedPtrNodeStack& stack, uint256 const& target, intr_ptr::SharedPtr child) { // walk the tree up from through the inner nodes to the root_ // update hashes and links // stack is a path of inner nodes up to, but not including, child // child can be an inner node or a leaf XRPL_ASSERT( (state_ != SHAMapState::Synching) && (state_ != SHAMapState::Immutable), "xrpl::SHAMap::dirtyUp : valid state"); XRPL_ASSERT(child && (child->cowid() == cowid_), "xrpl::SHAMap::dirtyUp : valid child input"); while (!stack.empty()) { auto node = intr_ptr::dynamicPointerCast(stack.top().first); SHAMapNodeID const nodeID = stack.top().second; stack.pop(); XRPL_ASSERT(node, "xrpl::SHAMap::dirtyUp : non-null node"); int const branch = selectBranch(nodeID, target); XRPL_ASSERT(branch >= 0, "xrpl::SHAMap::dirtyUp : valid branch"); node = unshareNode(std::move(node), nodeID); node->setChild(branch, std::move(child)); child = std::move(node); } } SHAMapLeafNode* SHAMap::walkTowardsKey(uint256 const& id, SharedPtrNodeStack* stack) const { XRPL_ASSERT( stack == nullptr || stack->empty(), "xrpl::SHAMap::walkTowardsKey : empty stack input"); auto inNode = root_; SHAMapNodeID nodeID; while (inNode->isInner()) { if (stack != nullptr) stack->emplace(inNode, nodeID); auto const inner = intr_ptr::staticPointerCast(inNode); auto const branch = selectBranch(nodeID, id); if (inner->isEmptyBranch(branch)) return nullptr; inNode = descendThrow(*inner, branch); nodeID = nodeID.getChildNodeID(branch); } if (stack != nullptr) stack->emplace(inNode, nodeID); return safeDowncast(inNode.get()); } SHAMapLeafNode* SHAMap::findKey(uint256 const& id) const { SHAMapLeafNode* leaf = walkTowardsKey(id); // NOLINT(misc-const-correctness) if ((leaf != nullptr) && leaf->peekItem()->key() != id) leaf = nullptr; return leaf; } intr_ptr::SharedPtr SHAMap::fetchNodeFromDB(SHAMapHash const& hash) const { XRPL_ASSERT(backed_, "xrpl::SHAMap::fetchNodeFromDB : is backed"); auto obj = f_.db().fetchNodeObject(hash.asUInt256(), ledgerSeq_); return finishFetch(hash, obj); } intr_ptr::SharedPtr SHAMap::finishFetch(SHAMapHash const& hash, std::shared_ptr const& object) const { XRPL_ASSERT(backed_, "xrpl::SHAMap::finishFetch : is backed"); try { if (!object) { if (full_) { full_ = false; f_.missingNodeAcquireBySeq(ledgerSeq_, hash.asUInt256()); } return {}; } auto node = SHAMapTreeNode::makeFromPrefix(makeSlice(object->getData()), hash); if (node) canonicalize(hash, node); return node; } catch (std::runtime_error const& e) { JLOG(journal_.warn()) << "finishFetch exception: " << e.what(); } catch (...) { JLOG(journal_.warn()) << "finishFetch exception: unknown exception: " << hash; } return {}; } // See if a sync filter has a node intr_ptr::SharedPtr SHAMap::checkFilter(SHAMapHash const& hash, SHAMapSyncFilter* filter) const { if (auto nodeData = filter->getNode(hash)) { try { auto node = SHAMapTreeNode::makeFromPrefix(makeSlice(*nodeData), hash); if (node) { filter->gotNode(true, hash, ledgerSeq_, std::move(*nodeData), node->getType()); if (backed_) canonicalize(hash, node); } return node; } catch (std::exception const& x) { JLOG(f_.journal().warn()) << "Invalid node/data, hash=" << hash << ": " << x.what(); } } return {}; } // Get a node without throwing // Used on maps where missing nodes are expected intr_ptr::SharedPtr SHAMap::fetchNodeNT(SHAMapHash const& hash, SHAMapSyncFilter* filter) const { auto node = cacheLookup(hash); if (node) return node; if (backed_) { node = fetchNodeFromDB(hash); if (node) { canonicalize(hash, node); return node; } } if (filter != nullptr) node = checkFilter(hash, filter); return node; } intr_ptr::SharedPtr SHAMap::fetchNodeNT(SHAMapHash const& hash) const { auto node = cacheLookup(hash); if (!node && backed_) node = fetchNodeFromDB(hash); return node; } // Throw if the node is missing intr_ptr::SharedPtr SHAMap::fetchNode(SHAMapHash const& hash) const { auto node = fetchNodeNT(hash); if (!node) Throw(type_, hash); return node; } SHAMapTreeNode* SHAMap::descendThrow(SHAMapInnerNode* parent, int branch) const { SHAMapTreeNode* ret = descend(parent, branch); // NOLINT(misc-const-correctness) if ((ret == nullptr) && !parent->isEmptyBranch(branch)) Throw(type_, parent->getChildHash(branch)); return ret; } intr_ptr::SharedPtr SHAMap::descendThrow(SHAMapInnerNode& parent, int branch) const { intr_ptr::SharedPtr ret = descend(parent, branch); if (!ret && !parent.isEmptyBranch(branch)) Throw(type_, parent.getChildHash(branch)); return ret; } SHAMapTreeNode* SHAMap::descend(SHAMapInnerNode* parent, int branch) const { SHAMapTreeNode* ret = parent->getChildPointer(branch); // NOLINT(misc-const-correctness) if ((ret != nullptr) || !backed_) return ret; intr_ptr::SharedPtr node = fetchNodeNT(parent->getChildHash(branch)); if (!node) return nullptr; node = parent->canonicalizeChild(branch, std::move(node)); return node.get(); } intr_ptr::SharedPtr SHAMap::descend(SHAMapInnerNode& parent, int branch) const { intr_ptr::SharedPtr node = parent.getChild(branch); if (node || !backed_) return node; node = fetchNode(parent.getChildHash(branch)); if (!node) return {}; node = parent.canonicalizeChild(branch, std::move(node)); return node; } // Gets the node that would be hooked to this branch, // but doesn't hook it up. intr_ptr::SharedPtr SHAMap::descendNoStore(SHAMapInnerNode& parent, int branch) const { intr_ptr::SharedPtr ret = parent.getChild(branch); if (!ret && backed_) ret = fetchNode(parent.getChildHash(branch)); return ret; } std::pair SHAMap::descend( SHAMapInnerNode* parent, SHAMapNodeID const& parentID, int branch, SHAMapSyncFilter* filter) const { XRPL_ASSERT(parent->isInner(), "xrpl::SHAMap::descend : valid parent input"); XRPL_ASSERT( (branch >= 0) && (branch < kBranchFactor), "xrpl::SHAMap::descend : valid branch input"); XRPL_ASSERT( !parent->isEmptyBranch(branch), "xrpl::SHAMap::descend : parent branch is non-empty"); SHAMapTreeNode* child = parent->getChildPointer(branch); // NOLINT(misc-const-correctness) if (child == nullptr) { auto const& childHash = parent->getChildHash(branch); intr_ptr::SharedPtr childNode = fetchNodeNT(childHash, filter); if (childNode) { childNode = parent->canonicalizeChild(branch, std::move(childNode)); child = childNode.get(); } } return std::make_pair(child, parentID.getChildNodeID(branch)); } SHAMapTreeNode* SHAMap::descendAsync( SHAMapInnerNode* parent, int branch, SHAMapSyncFilter* filter, bool& pending, descendCallback&& callback) const { pending = false; SHAMapTreeNode* ret = parent->getChildPointer(branch); // NOLINT(misc-const-correctness) if (ret != nullptr) return ret; auto const& hash = parent->getChildHash(branch); auto ptr = cacheLookup(hash); if (!ptr) { if (filter != nullptr) ptr = checkFilter(hash, filter); if (!ptr && backed_) { f_.db().asyncFetch( hash.asUInt256(), ledgerSeq_, [this, hash, cb{std::move(callback)}](std::shared_ptr const& object) { auto node = finishFetch(hash, object); cb(node, hash); }); pending = true; return nullptr; } } if (ptr) ptr = parent->canonicalizeChild(branch, std::move(ptr)); return ptr.get(); } template intr_ptr::SharedPtr SHAMap::unshareNode(intr_ptr::SharedPtr node, SHAMapNodeID const& nodeID) { // make sure the node is suitable for the intended operation (copy on write) XRPL_ASSERT(node->cowid() <= cowid_, "xrpl::SHAMap::unshareNode : node valid for cowid"); if (node->cowid() != cowid_) { // have a CoW XRPL_ASSERT(state_ != SHAMapState::Immutable, "xrpl::SHAMap::unshareNode : not immutable"); node = intr_ptr::staticPointerCast(node->clone(cowid_)); if (nodeID.isRoot()) root_ = node; } return node; } SHAMapLeafNode* SHAMap::belowHelper( intr_ptr::SharedPtr node, SharedPtrNodeStack& stack, int branch, std::tuple, std::function> const& loopParams) const { auto& [init, cmp, incr] = loopParams; if (node->isLeaf()) { auto n = intr_ptr::staticPointerCast(node); stack.push({node, {kLeafDepth, n->peekItem()->key()}}); return n.get(); } auto inner = intr_ptr::staticPointerCast(node); if (stack.empty()) { stack.emplace(inner, SHAMapNodeID{}); } else { stack.emplace(inner, stack.top().second.getChildNodeID(branch)); } for (int i = init; cmp(i);) { if (!inner->isEmptyBranch(i)) { node.adopt(descendThrow(inner.get(), i)); XRPL_ASSERT(!stack.empty(), "xrpl::SHAMap::belowHelper : non-empty stack"); if (node->isLeaf()) { auto n = intr_ptr::staticPointerCast(node); stack.push({n, {kLeafDepth, n->peekItem()->key()}}); return n.get(); } inner = intr_ptr::staticPointerCast(node); stack.emplace(inner, stack.top().second.getChildNodeID(branch)); i = init; // descend and reset loop } else { incr(i); // scan next branch } } return nullptr; } SHAMapLeafNode* SHAMap::lastBelow(intr_ptr::SharedPtr node, SharedPtrNodeStack& stack, int branch) const { auto init = kBranchFactor - 1; auto cmp = [](int i) { return i >= 0; }; auto incr = [](int& i) { --i; }; return belowHelper(node, stack, branch, {init, cmp, incr}); } SHAMapLeafNode* SHAMap::firstBelow(intr_ptr::SharedPtr node, SharedPtrNodeStack& stack, int branch) const { auto init = 0; auto cmp = [](int i) { return i <= kBranchFactor; }; auto incr = [](int& i) { ++i; }; return belowHelper(node, stack, branch, {init, cmp, incr}); } static boost::intrusive_ptr const kNoItem; boost::intrusive_ptr const& SHAMap::onlyBelow(SHAMapTreeNode* node) const { // If there is only one item below this node, return it while (!node->isLeaf()) { SHAMapTreeNode* nextNode = nullptr; auto inner = safeDowncast(node); for (int i = 0; i < kBranchFactor; ++i) { if (!inner->isEmptyBranch(i)) { if (nextNode != nullptr) return kNoItem; nextNode = descendThrow(inner, i); } } if (nextNode == nullptr) { // LCOV_EXCL_START UNREACHABLE("xrpl::SHAMap::onlyBelow : no next node"); return kNoItem; // LCOV_EXCL_STOP } node = nextNode; } // An inner node must have at least one leaf // below it, unless it's the root_ auto const leaf = safeDowncast(node); XRPL_ASSERT( leaf->peekItem() || (leaf == root_.get()), "xrpl::SHAMap::onlyBelow : valid inner node"); return leaf->peekItem(); } SHAMapLeafNode const* SHAMap::peekFirstItem(SharedPtrNodeStack& stack) const { XRPL_ASSERT(stack.empty(), "xrpl::SHAMap::peekFirstItem : empty stack input"); SHAMapLeafNode const* node = firstBelow(root_, stack); if (node == nullptr) { while (!stack.empty()) stack.pop(); return nullptr; } return node; } SHAMapLeafNode const* SHAMap::peekNextItem(uint256 const& id, SharedPtrNodeStack& stack) const { XRPL_ASSERT(!stack.empty(), "xrpl::SHAMap::peekNextItem : non-empty stack input"); XRPL_ASSERT(stack.top().first->isLeaf(), "xrpl::SHAMap::peekNextItem : stack starts with leaf"); stack.pop(); while (!stack.empty()) { auto [node, nodeID] = stack.top(); XRPL_ASSERT(!node->isLeaf(), "xrpl::SHAMap::peekNextItem : another node is not leaf"); auto inner = intr_ptr::staticPointerCast(node); for (auto i = selectBranch(nodeID, id) + 1; i < kBranchFactor; ++i) { if (!inner->isEmptyBranch(i)) { node = descendThrow(*inner, i); auto leaf = firstBelow(node, stack, i); if (leaf == nullptr) Throw(type_, id); XRPL_ASSERT(leaf->isLeaf(), "xrpl::SHAMap::peekNextItem : leaf is valid"); return leaf; } } stack.pop(); } // must be last item return nullptr; } boost::intrusive_ptr const& SHAMap::peekItem(uint256 const& id) const { SHAMapLeafNode const* leaf = findKey(id); if (leaf == nullptr) return kNoItem; return leaf->peekItem(); } boost::intrusive_ptr const& SHAMap::peekItem(uint256 const& id, SHAMapHash& hash) const { SHAMapLeafNode const* leaf = findKey(id); if (leaf == nullptr) return kNoItem; hash = leaf->getHash(); return leaf->peekItem(); } SHAMap::ConstIterator SHAMap::upperBound(uint256 const& id) const { SharedPtrNodeStack stack; walkTowardsKey(id, &stack); while (!stack.empty()) { auto [node, nodeID] = stack.top(); if (node->isLeaf()) { auto leaf = safeDowncast(node.get()); if (leaf->peekItem()->key() > id) return ConstIterator(this, leaf->peekItem().get(), std::move(stack)); } else { auto inner = intr_ptr::staticPointerCast(node); for (auto branch = selectBranch(nodeID, id) + 1; branch < kBranchFactor; ++branch) { if (!inner->isEmptyBranch(branch)) { node = descendThrow(*inner, branch); auto leaf = firstBelow(node, stack, branch); if (leaf == nullptr) Throw(type_, id); return ConstIterator(this, leaf->peekItem().get(), std::move(stack)); } } } stack.pop(); } return end(); } SHAMap::ConstIterator SHAMap::lowerBound(uint256 const& id) const { SharedPtrNodeStack stack; walkTowardsKey(id, &stack); while (!stack.empty()) { auto [node, nodeID] = stack.top(); if (node->isLeaf()) { auto leaf = safeDowncast(node.get()); if (leaf->peekItem()->key() < id) return ConstIterator(this, leaf->peekItem().get(), std::move(stack)); } else { auto inner = intr_ptr::staticPointerCast(node); for (int branch = selectBranch(nodeID, id) - 1; branch >= 0; --branch) { if (!inner->isEmptyBranch(branch)) { node = descendThrow(*inner, branch); auto leaf = lastBelow(node, stack, branch); if (leaf == nullptr) Throw(type_, id); return ConstIterator(this, leaf->peekItem().get(), std::move(stack)); } } } stack.pop(); } // TODO: what to return here? return end(); } bool SHAMap::hasItem(uint256 const& id) const { return (findKey(id) != nullptr); } bool SHAMap::delItem(uint256 const& id) { // delete the item with this ID XRPL_ASSERT(state_ != SHAMapState::Immutable, "xrpl::SHAMap::delItem : not immutable"); SharedPtrNodeStack stack; walkTowardsKey(id, &stack); if (stack.empty()) Throw(type_, id); auto leaf = intr_ptr::dynamicPointerCast(stack.top().first); stack.pop(); if (!leaf || (leaf->peekItem()->key() != id)) return false; SHAMapNodeType const type = leaf->getType(); using TreeNodeType = intr_ptr::SharedPtr; // What gets attached to the end of the chain (For now, nothing, since we deleted the leaf) TreeNodeType prevNode; while (!stack.empty()) { auto node = intr_ptr::staticPointerCast(stack.top().first); SHAMapNodeID const nodeID = stack.top().second; stack.pop(); node = unshareNode(std::move(node), nodeID); node->setChild( selectBranch(nodeID, id), std::move(prevNode)); // NOLINT(bugprone-use-after-move) XRPL_ASSERT( not prevNode, // NOLINT(bugprone-use-after-move) "xrpl::SHAMap::delItem : prevNode should be nullptr after std::move"); if (!nodeID.isRoot()) { // we may have made this a node with 1 or 0 children // And, if so, we need to remove this branch int const bc = node->getBranchCount(); if (bc == 0) { // no children below this branch // // Note: This is unnecessary due to the std::move above but left here for safety prevNode = TreeNodeType{}; } else if (bc == 1) { // If there's only one item, pull up on the thread auto item = onlyBelow(node.get()); if (item) { for (int i = 0; i < kBranchFactor; ++i) { if (!node->isEmptyBranch(i)) { node->setChild(i, TreeNodeType{}); break; } } prevNode = makeTypedLeaf(type, item, node->cowid()); } else { prevNode = std::move(node); } } else { // This node is now the end of the branch prevNode = std::move(node); } } } return true; } bool SHAMap::addGiveItem(SHAMapNodeType type, boost::intrusive_ptr item) { XRPL_ASSERT(state_ != SHAMapState::Immutable, "xrpl::SHAMap::addGiveItem : not immutable"); XRPL_ASSERT(type != SHAMapNodeType::TnInner, "xrpl::SHAMap::addGiveItem : valid type input"); // add the specified item, does not update uint256 const tag = item->key(); SharedPtrNodeStack stack; walkTowardsKey(tag, &stack); if (stack.empty()) Throw(type_, tag); auto [node, nodeID] = stack.top(); stack.pop(); if (node->isLeaf()) { auto leaf = intr_ptr::staticPointerCast(node); if (leaf->peekItem()->key() == tag) return false; } node = unshareNode(std::move(node), nodeID); if (node->isInner()) { // easy case, we end on an inner node auto inner = intr_ptr::staticPointerCast(node); int const branch = selectBranch(nodeID, tag); XRPL_ASSERT( inner->isEmptyBranch(branch), "xrpl::SHAMap::addGiveItem : inner branch is empty"); inner->setChild(branch, makeTypedLeaf(type, std::move(item), cowid_)); } else { // this is a leaf node that has to be made an inner node holding two // items auto leaf = intr_ptr::staticPointerCast(node); auto otherItem = leaf->peekItem(); XRPL_ASSERT( otherItem && (tag != otherItem->key()), "xrpl::SHAMap::addGiveItem : non-null item"); node = intr_ptr::makeShared(node->cowid()); unsigned int b1 = 0, b2 = 0; while ((b1 = selectBranch(nodeID, tag)) == (b2 = selectBranch(nodeID, otherItem->key()))) { stack.emplace(node, nodeID); // we need a new inner node, since both go on same branch at this // level nodeID = nodeID.getChildNodeID(b1); node = intr_ptr::makeShared(cowid_); } // we can add the two leaf nodes here XRPL_ASSERT(node->isInner(), "xrpl::SHAMap::addGiveItem : node is inner"); auto inner = safeDowncast(node.get()); inner->setChild(b1, makeTypedLeaf(type, std::move(item), cowid_)); inner->setChild(b2, makeTypedLeaf(type, std::move(otherItem), cowid_)); } dirtyUp(stack, tag, node); return true; } bool SHAMap::addItem(SHAMapNodeType type, boost::intrusive_ptr item) { return addGiveItem(type, std::move(item)); } SHAMapHash SHAMap::getHash() const { auto hash = root_->getHash(); if (hash.isZero()) { const_cast(*this).unshare(); hash = root_->getHash(); } return hash; } namespace { // Recompute hashes bottom-up for the subtree rooted at `node`, descending // only into dirty (cowid != 0) resident children — the hash side of // walkSubTree, without flushing/sharing. `node` must itself be dirty. // // Thread-safety: dirty nodes are uniquely owned by the current cowid, and the // subtrees hanging off distinct branches are disjoint, so two callers handed // children of different branches never touch the same node. Reads of clean // (cowid 0) children's cached hashes via updateHashDeep are read-only and safe // to race. void recomputeSubtreeHashes(SHAMapTreeNode* node) { if (node->isLeaf()) { node->updateHash(); return; } auto* inner = safeDowncast(node); for (int branch = 0; branch < SHAMapInnerNode::kBranchFactor; ++branch) { if (inner->isEmptyBranch(branch)) continue; auto* child = inner->getChildPointer(branch); if (child && (child->cowid() != 0)) recomputeSubtreeHashes(child); } inner->updateHashDeep(); } } // namespace SHAMapHash SHAMap::updateHashesParallel(int workers) { // Nothing dirtied since the last settle: the cached root hash is current. // (root_ is always present, matching getHash()'s invariant.) if (root_->cowid() == 0) return root_->getHash(); if (root_->isLeaf()) { root_->updateHash(); return root_->getHash(); } auto* rootInner = safeDowncast(root_.get()); // Gather the root's dirty, resident top-level subtrees. These are // independent and can be recomputed concurrently. std::vector subtrees; for (int branch = 0; branch < kBranchFactor; ++branch) { if (rootInner->isEmptyBranch(branch)) continue; auto* child = rootInner->getChildPointer(branch); if (child && (child->cowid() != 0)) subtrees.push_back(child); } if (workers <= 0) workers = static_cast(std::thread::hardware_concurrency()); if (workers <= 1 || subtrees.size() <= 1) { for (auto* s : subtrees) recomputeSubtreeHashes(s); } else { int const nthreads = std::min(workers, static_cast(subtrees.size())); std::atomic next{0}; std::vector> tasks; tasks.reserve(nthreads); for (int t = 0; t < nthreads; ++t) { tasks.push_back(std::async(std::launch::async, [&subtrees, &next] { for (std::size_t i = next++; i < subtrees.size(); i = next++) recomputeSubtreeHashes(subtrees[i]); })); } for (auto& task : tasks) task.get(); } // All top-level subtree hashes are now current; finish at the root. rootInner->updateHashDeep(); return rootInner->getHash(); } bool SHAMap::updateGiveItem(SHAMapNodeType type, boost::intrusive_ptr item) { // can't change the tag but can change the hash uint256 const tag = item->key(); XRPL_ASSERT(state_ != SHAMapState::Immutable, "xrpl::SHAMap::updateGiveItem : not immutable"); SharedPtrNodeStack stack; walkTowardsKey(tag, &stack); if (stack.empty()) Throw(type_, tag); auto node = intr_ptr::dynamicPointerCast(stack.top().first); auto nodeID = stack.top().second; stack.pop(); if (!node || (node->peekItem()->key() != tag)) { // LCOV_EXCL_START UNREACHABLE("xrpl::SHAMap::updateGiveItem : invalid node"); return false; // LCOV_EXCL_STOP } if (node->getType() != type) { JLOG(journal_.fatal()) << "SHAMap::updateGiveItem: cross-type change!"; return false; } node = unshareNode(std::move(node), nodeID); if (node->setItem(item)) dirtyUp(stack, tag, node); return true; } bool SHAMap::fetchRoot(SHAMapHash const& hash, SHAMapSyncFilter* filter) { if (hash == root_->getHash()) return true; if (auto stream = journal_.trace()) { if (type_ == SHAMapType::TRANSACTION) { stream << "Fetch root TXN node " << hash; } else if (type_ == SHAMapType::STATE) { stream << "Fetch root STATE node " << hash; } else { stream << "Fetch root SHAMap node " << hash; } } auto newRoot = fetchNodeNT(hash, filter); if (newRoot) { root_ = newRoot; XRPL_ASSERT(root_->getHash() == hash, "xrpl::SHAMap::fetchRoot : root hash do match"); return true; } return false; } /** Replace a node with a shareable node. This code handles two cases: 1) An unshared, unshareable node needs to be made shareable so immutable SHAMap's can have references to it. 2) An unshareable node is shared. This happens when you make a mutable snapshot of a mutable SHAMap. @note The node must have already been unshared by having the caller first call SHAMapTreeNode::unshare(). */ intr_ptr::SharedPtr SHAMap::writeNode(NodeObjectType t, intr_ptr::SharedPtr node) const { XRPL_ASSERT(node->cowid() == 0, "xrpl::SHAMap::writeNode : valid input node"); XRPL_ASSERT(backed_, "xrpl::SHAMap::writeNode : is backed"); canonicalize(node->getHash(), node); Serializer s; node->serializeWithPrefix(s); f_.db().store(t, std::move(s.modData()), node->getHash().asUInt256(), ledgerSeq_); return node; } // We can't modify an inner node someone else might have a // pointer to because flushing modifies inner nodes -- it // makes them point to canonical/shared nodes. template intr_ptr::SharedPtr SHAMap::preFlushNode(intr_ptr::SharedPtr node) const { // A shared node should never need to be flushed // because that would imply someone modified it XRPL_ASSERT(node->cowid(), "xrpl::SHAMap::preFlushNode : valid input node"); if (node->cowid() != cowid_) { // Node is not uniquely ours, so unshare it before // possibly modifying it node = intr_ptr::staticPointerCast(node->clone(cowid_)); } return node; } int SHAMap::unshare() { // Don't share nodes with parent map return walkSubTree(false, NodeObjectType::Unknown); } int SHAMap::flushDirty(NodeObjectType t) { // We only write back if this map is backed. return walkSubTree(backed_, t); } int SHAMap::walkSubTree(bool doWrite, NodeObjectType t) { XRPL_ASSERT(!doWrite || backed_, "xrpl::SHAMap::walkSubTree : valid input"); int flushed = 0; if (!root_ || (root_->cowid() == 0)) return flushed; if (root_->isLeaf()) { // special case -- root_ is leaf root_ = preFlushNode(std::move(root_)); root_->updateHash(); root_->unshare(); if (doWrite) root_ = writeNode(t, std::move(root_)); return 1; } auto node = intr_ptr::staticPointerCast(root_); if (node->isEmpty()) { // replace empty root with a new empty root root_ = intr_ptr::makeShared(0); return 1; } // Stack of {parent,index,child} pointers representing // inner nodes we are in the process of flushing using StackEntry = std::pair, int>; std::stack> stack; node = preFlushNode(std::move(node)); int pos = 0; // We can't flush an inner node until we flush its children while (true) { while (pos < kBranchFactor) { if (node->isEmptyBranch(pos)) { ++pos; } else { // No need to do I/O. If the node isn't linked, // it can't need to be flushed int const branch = pos; auto child = node->getChild(pos++); if (child && (child->cowid() != 0)) { // This is a node that needs to be flushed child = preFlushNode(std::move(child)); if (child->isInner()) { // save our place and work on this node stack.emplace(std::move(node), branch); node = intr_ptr::staticPointerCast(child); pos = 0; } else { // flush this leaf ++flushed; XRPL_ASSERT( node->cowid() == cowid_, "xrpl::SHAMap::walkSubTree : node cowid do " "match"); child->updateHash(); child->unshare(); if (doWrite) child = writeNode(t, std::move(child)); node->shareChild(branch, child); } } } } // update the hash of this inner node node->updateHashDeep(); // This inner node can now be shared node->unshare(); if (doWrite) node = intr_ptr::staticPointerCast(writeNode(t, std::move(node))); ++flushed; if (stack.empty()) break; auto parent = std::move(stack.top().first); pos = stack.top().second; stack.pop(); // Hook this inner node to its parent XRPL_ASSERT(parent->cowid() == cowid_, "xrpl::SHAMap::walkSubTree : parent cowid do match"); parent->shareChild(pos, node); // Continue with parent's next child, if any node = std::move(parent); ++pos; } // Last inner node is the new root_ root_ = std::move(node); return flushed; } void SHAMap::dump(bool hash) const { int leafCount = 0; JLOG(journal_.info()) << " MAP Contains"; std::stack> stack; stack.emplace(root_.get(), SHAMapNodeID()); do { auto [node, nodeID] = stack.top(); stack.pop(); JLOG(journal_.info()) << node->getString(nodeID); if (hash) { JLOG(journal_.info()) << "Hash: " << node->getHash(); } if (node->isInner()) { auto inner = safeDowncast(node); for (int i = 0; i < kBranchFactor; ++i) { if (!inner->isEmptyBranch(i)) { auto child = inner->getChildPointer(i); if (child != nullptr) { XRPL_ASSERT( child->getHash() == inner->getChildHash(i), "xrpl::SHAMap::dump : child hash do match"); stack.emplace(child, nodeID.getChildNodeID(i)); } } } } else { ++leafCount; } } while (!stack.empty()); JLOG(journal_.info()) << leafCount << " resident leaves"; } intr_ptr::SharedPtr SHAMap::cacheLookup(SHAMapHash const& hash) const { auto ret = f_.getTreeNodeCache()->fetch(hash.asUInt256()); XRPL_ASSERT(!ret || !ret->cowid(), "xrpl::SHAMap::cacheLookup : not found or zero cowid"); return ret; } void SHAMap::canonicalize(SHAMapHash const& hash, intr_ptr::SharedPtr& node) const { XRPL_ASSERT(backed_, "xrpl::SHAMap::canonicalize : is backed"); XRPL_ASSERT(node->cowid() == 0, "xrpl::SHAMap::canonicalize : valid node input"); XRPL_ASSERT(node->getHash() == hash, "xrpl::SHAMap::canonicalize : node hash do match"); f_.getTreeNodeCache()->canonicalizeReplaceClient(hash.asUInt256(), node); } void SHAMap::invariants() const { (void)getHash(); // update node hashes auto node = root_.get(); XRPL_ASSERT(node, "xrpl::SHAMap::invariants : non-null root node"); XRPL_ASSERT(!node->isLeaf(), "xrpl::SHAMap::invariants : root node is not leaf"); SharedPtrNodeStack stack; for (auto leaf = peekFirstItem(stack); leaf != nullptr; leaf = peekNextItem(leaf->peekItem()->key(), stack)) ; node->invariants(true); } } // namespace xrpl